#数据结构代码
用于考研408
【注意】为了避免使用二级指针,函数传参使用了C++中的引用传参&。
#数据结构三要素和三种结构
-
逻辑结构
- 集合结构
- 线性结构:一对一
- 线性表
- 栈
- 队列
- 串
- 树形结构:一对多
- 图形结构:多对多
-
数据运算(基本操作)
-
物理结构(存储结构)
- 顺序存储:物理上相邻
- 非顺序(离散)存储:物理上不相邻
- 链式存储
- 索引存储
- 散列存储
若采用顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的。
数据的存储结构会影响存储空间分配的方便程度。
数据的存储结构会影响对数据运算的速度。
#线性结构
#1.线性表linear list
#1.1 顺序表-C-静态分配
#include <stdio.h>
#include<stdbool.h> //引入bool数据类型
#define MaxSize 10 //定义最大长度
typedef int ElemType; //定义数据元素类型为整型
//顺序表
// c
typedef struct
{
int data[MaxSize]; //用静态的"数组"存放数据元素
int length; //顺序表的当前长度(总长度)
}SqList; //顺序表的类型定义
void InitList(SqList* L);
bool ListInsert(SqList* L,int i,int e);
bool ListDelete(SqList* L,int i,int* e);
ElemType GetElem(SqList L, int i);
int LocateElem(SqList L, ElemType e);
void TraverseList(SqList L);
/*关于销毁
- 静态分配:静态数组,系统自动回收空间。
- 动态分配:动态数组(malloc、 free),需要手动free。*/
int main() {
SqList L; //声明一个顺序表L
int number; //number为元素个数
ElemType value; //value为查找的元素值
InitList(&L); //初始化顺序表L
printf("请输入需要插入的元素个数:");
scanf("%d",&number);
printf("依次输入%d个元素:",number);
for(int i=1; i<=number; i++){
scanf("%d",&value);
ListInsert(&L, i, value); //在顺序表L的第1个位置插入元素value
}
TraverseList(L); //输出顺序表L
ListInsert(&L, 1, 11); //在顺序表L的第1个位置插入元素11
printf("插入之后的顺序表:");
TraverseList(L); //输出顺序表L
printf("L的第1个位置的元素值:");
printf("%d\n", GetElem(L, 1)); //获取顺序表L的第1个位置的元素值
printf("查找元素值为11的元素的位序:");
printf("%d\n", LocateElem(L, 11)); //查找元素值为11的元素,返回其位序
ElemType e;
ListDelete(&L, 1, &e); //删除顺序表L的第1个位置的元素,并用e返回其值
printf("返回刚删除的e的值:%d\n", e);
TraverseList(L); //输出顺序表L
return 0;
}
//----------------------------------------------------
//基本操作:初始化一个顺序表
void InitList(SqList* L){
for(int i=0; i<MaxSize; i++) //MaxSize是顺序表的最大长度L->length
L->data[i]=0; //将所有数据元素设置为默认初始值0
L->length=0; //顺序表初始长度为0
}
// 插入:在表L中的第i个位置上插入指定元素e。
//把新的数据插入的目标位置之后,把目标位置后面的数据依次向后进行移动。O(n)
bool ListInsert(SqList* L,int i,int e){
if(i<1 || i>L->length+1) //判断i的范围是否有效
return false;
if(L->length>=MaxSize) //当前存储空间已满,不能插入
return false;
for(int j=L->length; j>=i; j--) //将第i个元素及之后的元素后移
L->data[j]=L->data[j-1];
L->data[i-1]=e; //在位置i处放入新的数据e
L->length++; //长度加1
return true;
}
// 删除。删除表L中第i个位置的元素,并用e返回删除元素的值。
//O(n)
bool ListDelete(SqList* L,int i,int* e){
if(i<0||i>L->length)//判断i的范围是否有效
return false;
*e=L->data[i-1];//将被删除的元素赋值给e
for(int j=i;j<L->length;j++)//将第i个位置后的元素前移
L->data[j-1]=L->data[j];
L->length--;//线性表长度减1
return true;
}
// 按位查找操作。获取表L中第i个位置的元素的值。
//O(1)
ElemType GetElem(SqList L, int i){
if(i<0 || i>L.length)//判断i的范围是否有效
return NULL;
return L.data[i-1];
}
// 按值查找操作。在表L中查找具有给定关键字值的元素。
//O(n)
int LocateElem(SqList L, ElemType e){
for(int i=0;i<L.length;i++)
if(L.data[i]==e)
return i+1; //数组下标为i的元素值等于e,返回其位序i+1
return -1;//退出循环,说明查找失败
}
// 遍历
void TraverseList(SqList L){
for(int i=0; i<L.length; i++)
printf("%d ", L.data[i]);
printf("\n");
}请输入需要插入的元素个数:4 依次输入4个元素:1 2 3 4 1 2 3 4 插入之后的顺序表:11 1 2 3 4 L的第1个位置的元素值:11 查找元素值为11的元素的位序:1 返回刚删除的e的值:11 1 2 3 4
#1.2 单链表-Cpp-带头结点
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType; //定义数据元素类型为整型
//创建单链表
// cpp
typedef struct LNode{ //定义单链表结点类型
ElemType data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个节点
}LNode, *LinkList;
bool InitList(LinkList &L); //初始化
bool Empty(LinkList L); //判空
bool ListInsert(LinkList &L, int i, ElemType e);//插入
bool InsertNextNode(LNode *p, ElemType e); //后插
bool InsertPriorNode(LinkList &L, LNode *p, ElemType e);//前插
bool ListDelete(LinkList &L,int i,ElemType &e); //按照位序删除元素
bool DeleteNode(LinkList &L,LNode *p); //删除指定的某一个元素
void DestroyList(LinkList &L); //销毁
LNode* GetElem(LinkList L, int i); //按照位序查找
LNode* LocateElem(LinkList L, ElemType e);//按照给定的值查找
int Length(LinkList L); //链表长度
void TraverseList(LinkList L);//遍历输出链表的所有元素
LinkList CreateListTail(int n);//尾插法建立链表
int main()
{
LinkList L;
InitList(L);
L = CreateListTail(5);
printf("链表长度:%d\n", Length(L));
//------------------------测试代码--------------------
// 查找
//查找第一个元素
LNode *p = GetElem(L, 1);
if(p){
printf("查找第一个元素:%d\n", p->data);
}else{
printf("查找失败\n");
}
//查找3
p = LocateElem(L, 3);
if(p){
printf("查找3成功:%d\n", p->data);
}else{
printf("查找3失败\n");
}
// 插入
printf("在第1个位置插入100:");
ListInsert(L, 1, 100);
TraverseList(L);
printf("在第6个位置,后插入200:");
if( InsertNextNode(GetElem(L, 6), 200) ){
printf("后插成功\n");
}
else{
printf("后插失败\n");
}
TraverseList(L);
printf("在第1个位置,前插入300:");
if( InsertPriorNode(L, GetElem(L, 1), 300) ){
printf("前插成功\n");
}
else{
printf("前插失败\n");
}
TraverseList(L);
// 删除
ElemType e=0;
printf("删除第8个位置的元素:");
if( ListDelete(L, 8, e) ){
printf("删除成功,删除的元素是:%d\n", e);
}
else{
printf("删除失败\n");
}
TraverseList(L);
printf("删除结点1:");
if( DeleteNode(L, GetElem(L, 1))){
printf("删除成功\n");
}
else{
printf("删除失败\n");
}
TraverseList(L);
// 销毁
printf("销毁链表:");
DestroyList(L);
TraverseList(L);
//---------------------------------------------------
return 0;
}
//---------------------------------------------------
//初始化一个单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点
if (L==NULL) //内存不足,分配失败
return false;
L->next = NULL; //头结点之后暂时还没有节点
return true;
}
// 判断单链表是否为空(带头结点)
bool Empty(LinkList L){
return L==NULL ? true : false;
}
/**
*
* @brief 按位序插入(带头结点):在表L中的第i个位置上插入指定元素e。
* @details 在 i 位置插入e,相当于在 i 前面 i-1 位置后插入e,所以Get查找的是i-1。
* 时间复杂度O(n)
* @note step1.判断插入的是不是头结点,如果是则false;
* step2.循环找到第i-1个结点;
* step3.判断i-1的合法性;
* step4.在i-1之后插入新结点。
*
* @param L
* @param i
* @param e
* @return true
* @return false
*
*/
bool ListInsert(LinkList &L, int i, ElemType e){
//step1判断插入的是不是头结点,如果是则false。
if(i<1) //位序0是头结点
return false;
//step2循环找到第i-1个结点。
//这样p就是i前一个结点,插入p的后面
LNode *p = GetElem(L, i-1);
//step3判断i-1的合法性。
if(p==NULL) //i-1值不合法
return false;
//step4在i-1之后插入新结点。
LNode *s = (LNode *)malloc(sizeof(LNode));//新结点s
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
//后插操作:在p结点之后插入元素e
//时间复杂度O(1)
bool InsertNextNode(LNode *p, ElemType e){
if (Empty(p))
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s==NULL) //内存分配失败
return false;
s->data = e; //用结点s保存数据元素e
s->next = p->next;
p->next = s; //将结点s连到p之后
return true;
}
//前插操作:在表L中的指定p结点之前插入元素e
//两种方法:
// 1是朴素的遍历一遍,然后在p-1后面插入e; 时间复杂度O(n)
// 2是在新建一个结点,存放p的内容,链接p->next,这样它就变成了p,这时候p就可以赋值为新的结点了。时间复杂度O(1)
bool InsertPriorNode(LinkList &L, LNode *p, ElemType e){
if (Empty(p) || p==L) //插入头结点or空结点前面
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s==NULL) //内存分配失败
return false;
s->next = p->next;
p->next = s; //新结点s连到p之后
s->data = p->data; //将p中元素复制到s中
p->data = e; //p中元素覆盖为e
return true;
}
/**
* @brief 按位序删除元素(带头结点):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
* @details 因为无法获取链表里面一个结点的上一个结点,所以这里直接获取 i-1 结点,来删除 i 结点。
* 时间复杂度O(n)
* @note step1.新建节点q指向要删除的i结点;
* step2.用e返回删除的结点内容;
* step3.i-1结点直接指向i的下一个结点(q->next),来断开i结点;
* step4.释放i的空间(释放q)。
*
* @param L
* @param i
* @param e
* @return true
* @return false
*/
bool ListDelete(LinkList &L, int i, ElemType &e){
if(i<1 || i>Length(L))
return false;
//因为无法获取链表里面一个结点的上一个结点,所以这里直接获取 i-1 结点,来删除 i 结点。
LNode *p = GetElem(L, i-1); //获取第i-1元素
if(p==NULL) //i-1值不合法
return false;
LNode *q = p->next; //令q指向被删除结点(拿到结点),也就是p->next
e = q->data; //用e返回元素的值
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
return true; //删除成功
}
/**
* @brief 指定结点删除:删除表L中指定的某一个结点p。
* @details 时间复杂度O(1)
* @note 解释:1->2->3->4。
* 若要删掉1,可以先令前两个数据交换,2->1->3->4;
* 再让1的指针链断开,令2指向3: 2->(1)-3->4;
* 把1断开,于是就是2->3->4。
*
*【注意】如果p结点是最后一个结点则无法使用这种,只能依次遍历。
*
* @param L
* @param p
* @return true
* @return false
*/
bool DeleteNode(LinkList &L, LNode *p){
if(p == NULL || p==L) //若删除的节点为空结点或者头结点,操作无效
return false;
if(p->next != NULL){ //p是最后一个结点,则next=NULL,操作无效
LNode *q = p->next; //定义一个q指针,令q指向*p的后继结点
p->data = q->data; //和后继结点交换数据域,相当于将p节点的后一个结点的数据赋值到p结点中
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
return true; //删除成功
}
return false;
}
//销毁一个单链表
//时间复杂度O(n)
void DestroyList(LinkList &L){
//循环释放各个数据结点
while(L->next != NULL){ //判断头结点是否有后继结点,直到头结点后再无其他结点结束循环
LNode *q = L; //令q指向被删除结点(拿到结点)
L = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
}
free(L); //释放头结点
L = NULL; //头指针指向NULL
}
//按位查找:获取表L中第i个位置的元素的值。
//时间复杂度O(n)
LNode* GetElem(LinkList L, int i)
{
if(i<0) //判断i是否合法。i<0,那么头结点也可以返回。
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=L; //L指向头结点,头结点是第0个结点(不存数据)
while(p!=NULL && j<i){ //寻找第i个结点
p=p->next; //让p指针依次往后移
j++;
}
return p;
}
//按值查找:在表L中查找具有给定关键字值的元素。
//时间复杂度O(n)
LNode* LocateElem(LinkList L, ElemType e) {
LNode *p = L->next;
//从第1个结点开始查找数据域为e的结点
while(p != NULL && p->data != e)
p = p->next;
return p; //找到后返回该结点指针,否则返回NULL
}
//求表的长度(带头结点,但是统计的时候不带头结点的数量)
//时间复杂度O(n)
int Length(LinkList L){
int len = 0; //统计表长
LNode *p=L;
while (p->next != NULL){
p = p->next;
len++;
}
return len;
}
//链表的遍历输出
void TraverseList(LinkList L){
if(!Empty(L)){
LNode *p, *x;
p = L;//将头指针赋值给p
while(p->next != NULL){
x = p->next;
printf("%d ", x->data);
p = p->next;
}
printf("\n");
}
else{
printf("链表为空!\n");
}
}
//尾插法建立链表
//时间复杂度O(n)
LinkList CreateListTail(int n) {//输入几个结点
LNode *p;
LNode *r;
int i;
//新建单链表L
LinkList L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;//头结点
L->data = 0;//防止脏数据
r = L;//尾指针等于当前头结点
//放入0 1 2 3...n
for(i=0; i<n; i++){
p = (LNode*)malloc(sizeof(LNode));//每次动态申请一个结构体空间存储
p->data = i;
r->next = p;
r = p;//尾指针后移
}
r->next = NULL;
return L;
}链表长度:5 查找第一个元素:0 查找3成功:3 在第1个位置插入100:100 0 1 2 3 4 在第6个位置,后插入200:后插成功 100 0 1 2 3 4 200 在第1个位置,前插入300:前插成功 300 100 0 1 2 3 4 200 删除第8个位置的元素:删除成功,删除的元素是:200 300 100 0 1 2 3 4 删除结点1:删除成功 100 0 1 2 3 4 销毁链表:链表为空!
#1.2 单链表-C-带头结点
#include<stdio.h>
#include<stdbool.h> //引入bool数据类型
#include<stdlib.h>
typedef int ElemType; //定义数据元素类型为整型
//创建单链表Singly Linked List
// c
// 要使用双指针初始化
// Define a structure for a node in the linked list
// 定义链表中节点的结构
typedef struct LNode{
ElemType data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个节点
}LNode, *LinkList;
bool InitList(LinkList* L); //初始化
bool Empty(LinkList L); //判空
bool ListInsert(LinkList L, int i, ElemType e);//插入
bool InsertNextNode(LNode *p, ElemType e); //后插
bool InsertPriorNode(LinkList L, LNode *p, ElemType e);//前插
bool ListDelete(LinkList L, int i, ElemType* e); //按照位序删除元素
bool DeleteNode(LinkList L,LNode *p); //删除指定的某一个元素
void DestroyList(LinkList* L); //销毁
LNode* GetElem(LinkList L, int i); //按照位序查找
LNode* LocateElem(LinkList L, ElemType e);//按照给定的值查找
int Length(LinkList L); //链表长度
void TraverseList(LinkList L);//遍历输出链表的所有元素
LinkList CreateListTail(int n);//尾插法建立链表
int main()
{
LinkList L;
InitList(&L);
L = CreateListTail(5);
printf("链表长度:%d\n", Length(L));
//-------------------------测试代码-----------------------
// 查找
//查找第一个元素
LNode *p = GetElem(L, 1);
if(p){
printf("查找第一个元素:%d\n", p->data);
}else{
printf("查找失败\n");
}
//查找3
p = LocateElem(L, 3);
if(p){
printf("查找3成功:%d\n", p->data);
}else{
printf("查找3失败\n");
}
// 插入
printf("在第1个位置插入100:");
ListInsert(L, 1, 100);
TraverseList(L);
printf("在第6个位置,后插入200:");
if( InsertNextNode(GetElem(L, 6), 200) ){
printf("后插成功\n");
}
else{
printf("后插失败\n");
}
TraverseList(L);
printf("在第1个位置,前插入300:");
if( InsertPriorNode(L, GetElem(L, 1), 300) ){
printf("前插成功\n");
}
else{
printf("前插失败\n");
}
TraverseList(L);
// 删除
ElemType e=0;
printf("删除第8个位置的元素:");
if( ListDelete(L, 8, &e) ){
printf("删除成功,删除的元素是:%d\n", e);
}
else{
printf("删除失败\n");
}
TraverseList(L);
printf("删除结点1:");
// printf("%d",GetElem(L, 1)->data);
if( DeleteNode(L, GetElem(L, 1))){
printf("删除成功\n");
}
else{
printf("删除失败\n");
}
TraverseList(L);
// 销毁
printf("销毁链表:");
DestroyList(&L);
TraverseList(L);
//-----------------------------------------
return 0;
}
//----------------------------------------------------------
//初始化一个单链表(带头结点)
bool InitList(LinkList *L) {
*L = (LinkList)malloc(sizeof(LNode)); // Allocate memory for the head node
if (*L == NULL) {
return false; // Allocation failed
}
(*L)->next = NULL; // Initialize the next pointer of the head node
return true;
}
// 判断单链表是否为空(带头结点)
bool Empty(LinkList L){
return L==NULL ? true : false;
}
/**
*
* @brief 按位序插入(带头结点):在表L中的第i个位置上插入指定元素e。
* @details 在 i 位置插入e,相当于在 i 前面 i-1 位置后插入e,所以Get查找的是i-1。
* 时间复杂度O(n)
* @note step1.判断插入的是不是头结点,如果是则false;
* step2.循环找到第i-1个结点;
* step3.判断i-1的合法性;
* step4.在i-1之后插入新结点。
*
* @param L
* @param i
* @param e
* @return true
* @return false
*
*/
bool ListInsert(LinkList L, int i, ElemType e){
//step1判断插入的是不是头结点,如果是则false。
if(i<1) //位序0是头结点
return false;
//step2循环找到第i-1个结点。
//这样p就是i前一个结点,插入p的后面
LNode *p = GetElem(L, i-1);
//step3判断i-1的合法性。
if(p==NULL) //i-1值不合法
return false;
//step4在i-1之后插入新结点。
LNode *newNode = (LNode *)malloc(sizeof(LNode));//新结点
newNode->data = e;
newNode->next = p->next;
p->next = newNode; //将结点newNode连到p之后
return true; //插入成功
}
//后插操作:在p结点之后插入元素e
//时间复杂度O(1)
bool InsertNextNode(LNode *p, ElemType e){
if (Empty(p))
return false;
LNode *newNode = (LNode *)malloc(sizeof(LNode));
if (newNode==NULL) //内存分配失败
return false;
newNode->data = e; //用结点newNode保存数据元素e
newNode->next = p->next;
p->next = newNode; //将结点newNode连到p之后
return true;
}
//前插操作:在表L中的指定p结点之前插入元素e
//两种方法:
// 1是朴素的遍历一遍,然后在p-1后面插入e; 时间复杂度O(n)
// 2是在新建一个结点,存放p的内容,链接p->next,这样它就变成了p,这时候p就可以赋值为新的结点了。时间复杂度O(1)
bool InsertPriorNode(LinkList L, LNode *p, ElemType e){
if (Empty(p) || p==L) //插入头结点or空结点前面
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s==NULL) //内存分配失败
return false;
s->next = p->next;
p->next = s; //新结点s连到p之后
s->data = p->data; //将p中元素复制到s中
p->data = e; //p中元素覆盖为e
return true;
}
/**
* @brief 按位序删除元素(带头结点):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
* @details 因为无法获取链表里面一个结点的上一个结点,所以这里直接获取 i-1 结点,来删除 i 结点。
* 时间复杂度O(n)
* @note step1.新建节点q指向要删除的i结点;
* step2.用e返回删除的结点内容;
* step3.i-1结点直接指向i的下一个结点(q->next),来断开i结点;
* step4.释放i的空间(释放q)。
*
* @param L
* @param i
* @return ElemType
*/
bool ListDelete(LinkList L, int i, ElemType* e){
if(i<1 || i>Length(L))
return false;
//因为无法获取链表里面一个结点的上一个结点,所以这里直接获取 i-1 结点,来删除 i 结点。
LNode *p = GetElem(L, i-1); //获取第i-1元素
if(p==NULL) //i-1值不合法
return false;
LNode *q = p->next; //令q指向被删除结点(拿到结点),也就是p->next
*e = q->data; //用e返回元素的值
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
return true; //删除成功
}
/**
* @brief 指定结点删除:删除表L中指定的某一个结点p。
* @details 时间复杂度O(1)
* @note 解释:1->2->3->4。
* 若要删掉1,可以先令前两个数据交换,2->1->3->4;
* 再让1的指针链断开,令2指向3: 2->(1)-3->4;
* 把1断开,于是就是2->3->4。
*
*【注意】如果p结点是最后一个结点则无法使用这种,只能依次遍历。
*
* @param L
* @param p
* @return true
* @return false
*/
bool DeleteNode(LinkList L, LNode *p){
if(p == NULL || p==L) //若删除的节点为空结点或者头结点,操作无效
return false;
if(p->next != NULL){ //p是最后一个结点,则next=NULL,操作无效
LNode *q = p->next; //定义一个q指针,令q指向*p的后继结点
p->data = q->data; //和后继结点交换数据域,相当于将p节点的后一个结点的数据赋值到p结点中
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
return true; //删除成功
}
return false;
}
//销毁一个单链表
//时间复杂度O(n)
void DestroyList(LinkList* L){
LNode *current = (*L)->next;
//循环释放各个数据结点
while(current != NULL){ //判断头结点是否有后继结点,直到头结点后再无其他结点结束循环
LNode *q = current; //令q指向被删除结点(拿到结点)
current = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
}
free(current); //释放头结点
free(*L);
*L = NULL; //头指针指向NULL
}
//按位查找:获取表L中第i个位置的元素的值。
//时间复杂度O(n)
LNode* GetElem(LinkList L, int i)
{
if(i<0) //判断i是否合法。i<0,那么头结点也可以返回。
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=L; //L指向头结点,头结点是第0个结点(不存数据)
while(p!=NULL && j<i){ //寻找第i个结点
p=p->next; //让p指针依次往后移
j++;
}
return p;
}
//按值查找:在表L中查找具有给定关键字值的元素。
//时间复杂度O(n)
LNode* LocateElem(LinkList L, ElemType e) {
LNode *p = L->next;
//从第1个结点开始查找数据域为e的结点
while(p != NULL && p->data != e)
p = p->next;
return p; //找到后返回该结点指针,否则返回NULL
}
//求表的长度(带头结点,但是统计的时候不带头结点的数量)
//时间复杂度O(n)
int Length(LinkList L){
int len = 0; //统计表长
LNode* p=L;
while (p->next != NULL){
p = p->next;
len++;
}
return len;
}
//链表的遍历输出
void TraverseList(LinkList L){
if(!Empty(L)){
LNode *p, *x;
p = L;//将头指针赋值给p
while(p->next != NULL){
x = p->next;
printf("%d ", x->data);
p = p->next;
}
printf("\n");
}
else{
printf("链表为空!\n");
}
}
//尾插法建立链表
//时间复杂度O(n)
LinkList CreateListTail(int n) {//输入几个结点
LNode *p;
LNode *r;
int i;
//新建单链表L
LinkList L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;//头结点
L->data = 0;//防止脏数据
r = L;//尾指针等于当前头结点
//放入0 1 2 3...n
for(i=0; i<n; i++){
p = (LNode*)malloc(sizeof(LNode));//每次动态申请一个结构体空间存储
p->data = i;
r->next = p;
r = p;//尾指针后移
}
r->next = NULL;
return L;
}链表长度:5 查找第一个元素:0 查找3成功:3 在第1个位置插入100:100 0 1 2 3 4 在第6个位置,后插入200:后插成功 100 0 1 2 3 4 200 在第1个位置,前插入300:前插成功 300 100 0 1 2 3 4 200 删除第8个位置的元素:删除成功,删除的元素是:200 300 100 0 1 2 3 4 删除结点1:删除成功 100 0 1 2 3 4 销毁链表:链表为空!
#1.3 双链表-Cpp
#include<stdio.h>
#include<stdbool.h> //引入bool数据类型
#include<stdlib.h>
typedef int ElemType;
// 创建双链表(Doubly Linked List)
// cpp
typedef struct DNode{
ElemType data; //数据域
struct DNode *prior, *next; //前驱和后继指针
}DNode, *DLinkList;
bool InitDLinkList(DLinkList &L);
bool Empty(DLinkList L);
int Length(DLinkList L);
bool InsertNextDNode(DNode *p, DNode *s);
bool DeleteNextDNode(DNode *p);
void DestroyDList(DLinkList &L);
bool BackwardTraverseList(DLinkList L_head);
void ForwardTraverseList(DLinkList L_tail);
//主函数
int main(){
DLinkList L;
InitDLinkList(L);
//插入
int n=5;
for(int i=1; i<n+1; i++){
DNode * p = (DNode*)malloc(sizeof(DNode));//每次动态申请一个结构体空间存储
DNode * tail=L;
p->data = i;
InsertNextDNode(tail,p);
tail = p; //尾指针后移
}
printf("Insert %d DNode: ",n);
BackwardTraverseList(L);
//删除L
DeleteNextDNode(L);
printf("DeleteNextDNode(L): ");
BackwardTraverseList(L);
//求双链表的长度
printf("Length of DLinkList is %d\n",Length(L));
//判断双链表是否为空
if(Empty(L))
printf("DLinkList is empty\n");
else
printf("DLinkList is not empty\n");
//销毁双链表
DestroyDList(L);
//向后遍历
printf("BackwardTraverseList: ");
if(!BackwardTraverseList(L)){
printf("DLinkList is empty\n");
}
return 0;
}
//------------------------------------------------------------------
//初始换双链表
bool InitDLinkList(DLinkList &L){
L = (DNode *) malloc(sizeof(DNode));//分配一个头结点
if(L == NULL) //内存不足,分配失败
return false;
L->prior = NULL; //头结点的前驱prior永远指向NULL
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
//判断双链表是否为空(带头结点)
bool Empty(DLinkList L){
if(L->next == NULL)
return true;
else
return false;
}
//求双链表的长度(带头结点)
int Length(DLinkList L){
DNode *p = L->next; //p指向第一个结点
int j=0; //计数器
while(p){
j++;
p = p->next;
}
return j;
}
//插入:在p结点之后插入s结点。
//就是更改前驱后继的指针,插入新结点。
bool InsertNextDNode(DNode *p, DNode *s){
if (p == NULL || s == NULL) //非法参数
return false;
//和后继的联系
s->next = p->next;
if(p->next != NULL){ //如果p结点有后继结点才可以进去这个后继节点,如果p是最后一个结点,则后继为NULL
p->next->prior = s;
}
//和前驱的联系
s->prior = p;
p->next = s;
return true;//插入成功
}
//删除:删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
if(p == NULL)
return false;
DNode *q = p->next; //p的后继结点q
if(q == NULL)
return false; //p没有后继结点
p->next = q->next; //使原本指向q的p的后继指向q的后继
if(q->next != NULL) //q结点不是最后一个结点
q->next->prior = p;
free(q); //释放结点空间
return true; //删除成功
}
//销毁一个双链表
void DestroyDList(DLinkList &L){
//循环释放各个数据结点
while(L->next != NULL){ //判断头结点是否有后继结点,直到头结点后再无其他结点结束循环
DeleteNextDNode(L); //删除L结点的后继结点
}
free(L); //释放头结点
L = NULL; //头指针指向NULL
}
//向后遍历双链表
bool BackwardTraverseList(DLinkList L_head){
if(L_head == NULL)
return false; //空链表
DNode *p = L_head->next;
while(p){
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return true;
}
//向前遍历
// 有bug
// void ForwardTraverseList(DLinkList L_tail){
// DNode *p = L_tail->next;
// while(p){
// printf("%d ",p->data);
// p = p->prior;
// }
// printf("\n");
// }Insert 5 DNode: 5 4 3 2 1 DeleteNextDNode(L): 4 3 2 1 Length of DLinkList is 4 DLinkList is not empty BackwardTraverseList: DLinkList is empty
#1.3 双链表-C
#include<stdio.h>
#include<stdbool.h> //引入bool数据类型
#include<stdlib.h>
typedef int ElemType;
// 创建双链表(Doubly Linked List)
// c
typedef struct DNode{
ElemType data; //数据域
struct DNode *prior, *next; //前驱和后继指针
}DNode, *DLinkList;
bool InitDLinkList(DLinkList* L);
bool Empty(DLinkList L);
int Length(DLinkList L);
bool InsertNextDNode(DNode *p, DNode *s);
bool DeleteNextDNode(DNode *p);
void DestroyDList(DLinkList* L);
bool BackwardTraverseList(DLinkList L_head);
void ForwardTraverseList(DLinkList L_tail);
//主函数
int main(){
DLinkList L;
InitDLinkList(&L);
//插入
int n=5;
for(int i=1; i<n+1; i++){
DNode * p = (DNode*)malloc(sizeof(DNode));//每次动态申请一个结构体空间存储
DNode * tail=L;
p->data = i;
InsertNextDNode(tail,p);
tail = p; //尾指针后移
}
printf("Insert %d DNode: ",n);
BackwardTraverseList(L);
//删除L
DeleteNextDNode(L);
printf("DeleteNextDNode(L): ");
BackwardTraverseList(L);
//求双链表的长度
printf("Length of DLinkList is %d\n",Length(L));
//判断双链表是否为空
if(Empty(L))
printf("DLinkList is empty\n");
else
printf("DLinkList is not empty\n");
//销毁双链表
DestroyDList(&L);
//向后遍历
printf("BackwardTraverseList: ");
if(!BackwardTraverseList(L)){
printf("DLinkList is empty\n");
}
return 0;
}
//------------------------------------------------------------------
//初始化双链表
bool InitDLinkList(DLinkList* L){
*L = (DLinkList)malloc(sizeof(DNode));//分配一个头结点
if(L == NULL) //内存不足,分配失败
return false;
(*L)->prior = NULL; //头结点的前驱prior永远指向NULL
(*L)->next = NULL; //头结点之后暂时还没有结点
return true;
}
//判断双链表是否为空(带头结点)
bool Empty(DLinkList L){
if(L->next == NULL)
return true;
else
return false;
}
//求双链表的长度(带头结点)
int Length(DLinkList L){
DNode *p = L->next; //p指向第一个结点
int j=0; //计数器
while(p){
j++;
p = p->next;
}
return j;
}
//插入:在p结点之后插入s结点。
//就是更改前驱后继的指针,插入新结点。
bool InsertNextDNode(DNode *p, DNode *s){
if (p == NULL || s == NULL) //非法参数
return false;
//和后继的联系
s->next = p->next;
if(p->next != NULL){ //如果p结点有后继结点才可以进去这个后继节点,如果p是最后一个结点,则后继为NULL
p->next->prior = s;
}
//和前驱的联系
s->prior = p;
p->next = s;
return true;//插入成功
}
//删除:删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
if(p == NULL)
return false;
DNode *q = p->next; //p的后继结点q
if(q == NULL)
return false; //p没有后继结点
p->next = q->next; //使原本指向q的p的后继指向q的后继
if(q->next != NULL) //q结点不是最后一个结点
q->next->prior = p;
free(q); //释放结点空间
return true; //删除成功
}
//销毁一个双链表
void DestroyDList(DLinkList* L){
//循环释放各个数据结点
while((*L)->next != NULL){ //判断头结点是否有后继结点,直到头结点后再无其他结点结束循环
DeleteNextDNode(*L); //删除L结点的后继结点
}
free(*L); //释放头结点
*L = NULL;
}
//向后遍历双链表
bool BackwardTraverseList(DLinkList L_head){
if(L_head == NULL)
return false; //空链表
DNode *p = L_head->next;
while(p){
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return true;
}
//向前遍历
// 有bug
// void ForwardTraverseList(DLinkList L_tail){
// DNode *p = L_tail->next;
// while(p){
// printf("%d ",p->data);
// p = p->prior;
// }
// printf("\n");
// }Insert 5 DNode: 5 4 3 2 1 DeleteNextDNode(L): 4 3 2 1 Length of DLinkList is 4 DLinkList is not empty BackwardTraverseList: DLinkList is empty
#2.栈Stack
#2.1 顺序栈-CPP-top=-1
#include<iostream>
#include<Windows.h>
using namespace std;
#define MaxSize 10 //栈最大可以存放的元素个数
typedef int ElemType; //顺序栈存储的数据类型、用int代替ElemType
//创建顺序栈
// cpp
typedef struct
{
ElemType* base; //栈底指针
int top; //栈顶的位置 如 0、1、2、3、4....MaxSize
} SqStack; //顺序栈的结构体定义
bool InitStack(SqStack& stack); //初始化栈
bool StackEmpty(SqStack stack);//判断是否为空
bool StackFull(SqStack stack); //判断是否已满
int GetStackSize(SqStack& stack);//获取顺序栈中元素个数
bool Push(SqStack& stack, ElemType value);//入栈
bool Pop(SqStack& stack, ElemType& value);//出栈
bool GetTop(SqStack& stack, ElemType& value);//获取栈顶的元素
void DestroyStack(SqStack& stack);//销毁栈、释放栈的内存
//--------------------------------------------------
void CreatStack(SqStack &stack){
int number, value = 0;
cout << "请输入需要插入的元素个数:";
cin >> number;
while (number > 0){
cin >> value;
Push(stack, value); //放入栈
number--;
value++;
}
}
int main()
{
SqStack stack; //创建顺序栈
InitStack(stack); //初始化顺序栈
//例如插入
//这里在测试时候,使用了两种方法
int value = 5; //插入5个元素
// while (value > 0){
// Push(stack, value); //放入栈
// value--;
// }
CreatStack(stack);
//获取栈顶的元素
GetTop(stack, value);
cout << "当前栈顶的元素是:" << value << endl;
//获取栈的元素个数
cout << "当前栈的元素个数是:" << GetStackSize(stack) << endl;
//出栈
cout << "出栈顺序:" << endl;
while (!StackEmpty(stack)){
Pop(stack, value);
cout << value << " ";
}
cout << endl;
//释放栈的内存
DestroyStack(stack);
//system("pause");
return 0;
}
//----------------------------------------------------------
//初始化顺序栈
bool InitStack(SqStack& stack){
//注意:这里使用new进行空间分配,所以在后面摧毁栈的时候需要delete释放空间
//动态分配一个ElemType类型MaxSize长度的空间,将地址给顺序栈Stack的栈底指针
stack.base = new ElemType[MaxSize];
//判断顺序栈的栈底指针(stack.base)是否为空,若无地址,则分配失败
if(!stack.base){
return false;
}
stack.top = -1; //初始化栈顶指针的位置为-1
return true;
}
//判断栈空
bool StackEmpty(SqStack stack){
if (stack.top == -1)
return true;
else
return false;
}
//判断栈满
bool StackFull(SqStack stack){
if (stack.top == MaxSize-1) //top的位置值等于MaxSize-1时栈满,因为是数据从0开始的
return true;
else
return false;
}
//顺序栈中元素个数
int GetStackSize(SqStack& stack){
return stack.top+1; //栈顶位置即top的数值,就是栈中元素的个数
}
/**
* @brief 顺序栈入栈:
* 开辟一个新的空间,栈顶+1,然后将数据存入stack.base[stack.top]所在的位置.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Push(SqStack& stack, ElemType value){
if (StackFull(stack)){
cout<<"栈满"<<endl;
return false;
}
//若栈未满,执行入栈操作
stack.top++; //栈顶自增1
stack.base[stack.top] = value; //以栈顶位置作为下标存储数据
return true;
}
/**
* @brief 顺序栈出栈:
* 读取栈顶stack.base[stack.top]的元素,然后使栈顶-1.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Pop(SqStack& stack, ElemType &value){
if (StackEmpty(stack)){
cout<<"栈为空"<<endl;
return false;
}
value = stack.base[stack.top]; //以栈顶位置作为下标的值赋值给value返回
stack.top--; //栈顶自减1
return true;
}
//读取栈顶元素
bool GetTop(SqStack& stack, ElemType &value){
if (StackEmpty(stack)){
cout<<"栈为空"<<endl;
return false;
}
value = stack.base[stack.top];
return true;
}
//销毁栈、释放栈的内存
void DestroyStack(SqStack& stack){
if(stack.base) { //若栈底指针分配有地址,则释放
delete stack.base; //释放栈底指针的地址
stack.top = -1; //令栈顶位置为0
stack.base = NULL; //将栈底指针指向空
cout<<"栈已释放内存!"<<endl;
}
}请输入需要插入的元素个数:3 3 6 9 当前栈顶的元素是:9 当前栈的元素个数是:3 出栈顺序: 9 6 3 栈已释放内存!
使用createStack
void PushStack(SqStack &stack){
int number;
ElemType value = 0;
cout << "请输入需要插入的元素个数:";
cin >> number;
while (number > 0){
cin >> value;
Push(stack, value); //放入栈
number--;
}
}
int main()
{
SqStack stack; //创建顺序栈
InitStack(stack); //初始化顺序栈
ElemType value = 5; //插入5个元素
//例如插入
// while (value > 0){
// Push(stack, value); //放入栈
// value--;
// }
PushStack(stack);
//获取栈顶的元素
GetTop(stack, value);
cout << "当前栈顶的元素是:" << value << endl;
//获取栈的元素个数
cout << "当前栈的元素个数是:" << GetStackSize(stack) << endl;
//出栈
cout << "出栈顺序:" << endl;
while (!StackEmpty(stack)){
Pop(stack, value);
cout << value << " ";
}
cout << endl;
//释放栈的内存
DestroyStack(stack);
system("pause");
return 0;
}请输入需要插入的元素个数:4 3 6 9 27 当前栈顶的元素是:27 当前栈的元素个数是:4 出栈顺序: 27 9 6 3 栈已释放内存! Press any key to continue . . .
#2.1 顺序栈-C-top=-1
#❗初始化有两种方式
栈顶指针top指向栈顶元素,一般存储的是数组的下标。(一般初始化时top=-1)
-
初始化时
top=-1,当前指向一个空的位置(元素从0开始)。放入元素之后,就指向那个元素,所以每次入栈先+1。top当前指向的位置就是栈顶。如果有abcde,5个元素,那么满栈top=4。
- 入栈:S.data[++S.top]=x;
- 出栈:x=S.data[S.top--];
- 获得栈顶元素:x=S.data[S.top];
- 满栈:stack->top == MaxSize-1
-
初始化时
top=0。top当前指向的位置是栈顶上面的一个没有元素的空位置。- 入栈:S.data[S.top++]=x;
- 出栈:x=S.data[--S.top];
- 获得栈顶元素:x=S.data[S.top-1];
#include<stdio.h>
#include<stdbool.h> //引入bool数据类型
#include <assert.h> //引入断言
#include<Windows.h>
#define MaxSize 5 //栈最大可以存放的元素个数
typedef int ElemType; //顺序栈存储的数据类型、用int代替ElemType
//创建顺序栈
// c
// top从-1开始
typedef struct
{
ElemType data[MaxSize]; //栈底指针
int top; //栈顶的位置 如 0、1、2、3、4....MaxSize
} SqStack; //顺序栈的结构体定义
bool InitStack(SqStack* stack); //初始化栈
bool StackEmpty(SqStack stack);//判断是否为空
bool StackFull(SqStack stack); //判断是否已满
int GetStackSize(SqStack stack);//获取顺序栈中元素个数
bool Push(SqStack* stack, ElemType value);//入栈
bool Pop(SqStack* stack, ElemType* value);//出栈
ElemType GetTop(SqStack stack);//获取栈顶的元素
//------------------------------------------------------
void PushStack(SqStack* stack){
int number;
ElemType value = 0;
printf("请输入需要插入的元素个数:");
scanf("%d",&number);
while (number > 0){
scanf("%d",&value);
Push(stack, value); //放入栈
number--;
}
}
int main()
{
SqStack stack; //创建顺序栈
InitStack(&stack); //初始化顺序栈
ElemType value = 5; //插入5个元素
//例如插入5,4,3,2,1
while (value > 0){
Push(&stack, value); //放入栈
value--;
}
//获取栈顶的元素
printf("当前栈顶的元素是:%d\n",GetTop(stack));
//获取栈的元素个数
printf("当前栈的元素个数是:%d\n",GetStackSize(stack));
//出栈1个
Pop(&stack, &value);
printf("出栈元素是:%d\n",value);
printf("出栈1个后的出栈顺序:\n");
while (!StackEmpty(stack)){
Pop(&stack, &value);
printf("%d ",value);
}
printf("\n");
return 0;
}
//----------------------------------------------------------------------
//初始化顺序栈
//初始化有两种方式:top=-1 或者 top=0
bool InitStack(SqStack* stack){
assert(stack); // 这里一定要断言,如果是空指针的话,就无法找到整个数组
stack->top = -1; //初始化栈顶指针的位置为-1
return true;
}
//判断栈空
bool StackEmpty(SqStack stack){
if (stack.top == -1)
return true;
else
return false;
}
//判断栈满
bool StackFull(SqStack stack){
//top的位置值等于MaxSize时栈满,因为数据是从0开始的
if (stack.top == MaxSize-1){
printf("栈满\n");
return true;
}
else
return false;
}
//顺序栈中元素个数
int GetStackSize(SqStack stack){
return stack.top+1; //栈顶位置即top的数值,就是栈中元素的个数
}
/**
* @brief 入栈:
* 开辟一个新的空间,栈顶+1,然后将数据存入stack.data[stack.top]所在的位置.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Push(SqStack* stack, ElemType value){
if (StackFull(*stack)){
return false;
}
//若栈未满,执行入栈操作
stack->top++; //栈顶自增1
stack->data[stack->top] = value; //以栈顶位置作为下标存储数据
return true;
}
/**
* @brief 出栈:
* 读取栈顶stack.data[stack.top]的元素,然后使栈顶-1.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Pop(SqStack* stack, ElemType* value){
if(StackEmpty(*stack)){
return false;
}
*value = stack->data[stack->top]; //以栈顶位置作为下标的值赋值给value返回
stack->top--; //栈顶自减1
return true;
}
//读取栈顶元素
ElemType GetTop(SqStack stack){
if(StackEmpty(stack)){
printf("栈为空");
return false;
}
return stack.data[stack.top];
}
//销毁栈、释放栈的内存
// 顺序栈是在声明栈时直接分配内存,并没有使用`malloc`函数,所以不需要手动`free`,函数运行结束后系统自动回收空间。当前栈顶的元素是:1 当前栈的元素个数是:5 出栈元素是:1 出栈1个后的出栈顺序: 2 3 4 5
#2.2 链栈-CPP
#include<iostream>
using namespace std;
typedef int SElemType; //链栈的数据元素类型假设为int整型
//创建链栈结构
// c++
typedef struct StackNode
{
SElemType data; //结点数据域
struct StackNode* next; //结点指针域
}StackNode, *LinkStack; //struct StackNode的结点形式、链栈形式别名
bool InitStack(LinkStack& stack); //初始化链栈
bool StackEmpty(LinkStack& stack); //链栈判空
int StackLength(LinkStack& stack); //计算链栈长度元素个数
bool PushStack(LinkStack& stack, SElemType value); //入栈
bool PopStack(LinkStack& stack, SElemType& value); //出栈
bool GetTop(LinkStack& stack, SElemType& value); //获取栈顶元素
bool StackPrint(LinkStack& stack); //遍历元素
void DestroyStack(LinkStack& stack); //销毁链栈,释放内存
int main()
{
//创建链栈
LinkStack stack;
SElemType value=1;
InitStack(stack);
cout << "检查栈是否为空?" << (StackEmpty(stack) ? "\t是" : "\t否") << endl;
int number = 0; //插入元素个数
cout << "请输入需要插入的元素个数:";
cin >> number;
while ((number--) > 0) {
PushStack(stack, value);//插入所输入元素
value++;
}
cout << "当前栈的元素个数:" << StackLength(stack) << endl;
GetTop(stack, value);
cout << "栈顶元素:" << value << endl;
StackPrint(stack);//遍历打印栈顶元素
cout << endl;
PopStack(stack, value);
cout << "出栈一次,栈顶元素为:" << value << endl;
StackPrint(stack);
DestroyStack(stack);
cout << endl << "栈已被销毁释放" << endl;
cout << "销毁栈后遍历栈结果:" << " ";
StackPrint(stack);
// system("pause");
return 0;
}
//链栈的初始化
bool InitStack(LinkStack& stack){
//构造一个空栈、栈顶指针置为空
stack = NULL;
return true;
}
//判断链栈是否为空
bool StackEmpty(LinkStack& stack){
return stack==NULL? true : false;
}
/**
* @brief 获取栈顶长度。
* 因为链表的最后一个节点的next指针是nullptr(或者说是NULL),代表链表的终止,
* 所以可以将链表的遍历条件设置为当前节点指针不等于nullptr,这样在遍历过程中,
* 当指针指向最后一个节点时,其next指针就会指向nullptr,循环条件就不再成立,
* 遍历结束,可以避免继续遍历下一个不合法的节点。
* @param stack
* @return int
*/
int StackLength(LinkStack& stack){
int length = 0;
StackNode* temp = stack;//创建临时指针temp与stack指向同一位置
while (temp != nullptr){
length++; //链栈长度即为栈中元素个数,循环一次,长度++
temp = temp->next; //temp指针下移一位
}
return length; //返回链栈长度
}
//入栈(前插法)
bool PushStack(LinkStack& stack, SElemType value){ //不用判栈满
StackNode* temp = new StackNode; //生成新结点temp
temp->data = value; //将新节点数据域置为value
temp->next = stack; //将新结点插入栈顶
stack = temp; //更新栈顶指针
return true;
}
//出栈:首先判空
bool PopStack(LinkStack& stack, SElemType &value){
if (StackEmpty(stack)){
return false;
}
value = stack->data; //将栈顶数据域元素赋值给value
StackNode* temp = stack; //创建一个temp指针,并将其指向 stack 指针所指向的内存地址,以便找到出栈位置并释放。
stack = stack->next; //令栈顶指针指向下一位结点,即更新栈顶指针
delete temp; //释放temp所指向的空间,即出栈元素所占的内存空间,temp本身会在函数结束后自动销毁。
return true;
}
//取栈顶元素
bool GetTop(LinkStack& stack, SElemType &value){
if (!StackEmpty(stack)){ //若栈不为空
value = stack->data; //返回栈顶元素
return true;
}
cout<<"栈为空"<<endl;
return false;
}
//遍历输出栈元素
bool StackPrint(LinkStack& stack){
if (stack != nullptr){
StackNode* temp = stack; //创建一个指针与stack指向同一位置
cout<<"出栈顺序:";
while (temp != nullptr){
cout << temp->data << " ";
temp = temp->next; //temp向下移动一位
}
return true;
}
cout<<"栈为空!"<<endl;
return false;
}
//销毁链栈,释放内存
void DestroyStack(LinkStack& stack){
StackNode* temp = new StackNode; //创建一个指针
while (stack != nullptr){
temp = stack; //使该临时指针与stack指向同一位置
stack = temp->next; //更新栈顶指针
delete temp; //释放临时指针
}
stack = nullptr;
}检查栈是否为空? 是 请输入需要插入的元素个数:5 当前栈的元素个数:5 栈顶元素:5 出栈顺序:5 4 3 2 1 出栈一次,栈顶元素为:5 出栈顺序:4 3 2 1 栈已被销毁释放 销毁栈后遍历栈结果: 栈为空!
#2.2 链栈-C
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h> //引入bool数据类型
typedef int SElemType; //链栈的数据元素类型假设为int整型
//创建链栈结构
// c
typedef struct StackNode
{
SElemType data; //结点数据域
struct StackNode* next; //结点指针域
}StackNode, *LinkStack; //struct StackNode的结点形式、链栈形式别名
void InitStack(LinkStack* stack); //初始化链栈
bool StackEmpty(LinkStack stack); //链栈判空
int StackLength(LinkStack stack); //计算链栈长度元素个数
bool PushStack(LinkStack* stack, SElemType value); //入栈
bool PopStack(LinkStack* stack, SElemType* value); //出栈
SElemType GetTop(LinkStack stack); //获取栈顶元素
bool StackPrint(LinkStack stack); //遍历元素
void DestroyStack(LinkStack* stack); //销毁链栈,释放内存
int main()
{
//创建链栈
LinkStack stack;
SElemType value=1;
InitStack(&stack);
int number = 4; //插入元素个数
while ((number--) > 0) {
PushStack(&stack, value);//插入所输入元素
value++;
}
printf("当前栈的元素个数:%d\n",StackLength(stack));
printf("栈顶元素:%d\n",GetTop(stack));
StackPrint(stack);//遍历打印栈顶元素
PopStack(&stack, &value);
printf("出栈一次,栈顶元素为:%d\n",value);
StackPrint(stack);
DestroyStack(&stack);
printf("栈已被销毁释放\n");
printf("销毁栈后遍历栈结果: ");
StackPrint(stack);
// system("pause");
return 0;
}
//链栈的初始化
void InitStack(LinkStack* stack){
//构造一个空栈、栈顶指针置为空
*stack = NULL;
}
//判断链栈是否为空
bool StackEmpty(LinkStack stack){
return stack==NULL? true : false;
}
/**
* @brief 获取栈顶长度。
* 因为链表的最后一个节点的next指针是nullptr(或者说是NULL),代表链表的终止,
* 所以可以将链表的遍历条件设置为当前节点指针不等于nullptr,这样在遍历过程中,
* 当指针指向最后一个节点时,其next指针就会指向nullptr,循环条件就不再成立,
* 遍历结束,可以避免继续遍历下一个不合法的节点。
* @param stack
* @return int
*/
int StackLength(LinkStack stack){
int length = 0;
StackNode* temp = stack;//创建临时指针temp与stack指向同一位置
while (temp != NULL){
length++; //链栈长度即为栈中元素个数,循环一次,长度++
temp = temp->next; //temp指针下移一位
}
return length; //返回链栈长度
}
//入栈(前插法)
bool PushStack(LinkStack* stack, SElemType value){ //不用判栈满
StackNode* temp = (StackNode*)malloc(sizeof(StackNode));//生成新结点temp
temp->data = value; //将新节点数据域置为value
temp->next = *stack; //将新结点插入栈顶
*stack = temp; //更新栈顶指针
return true;
}
//出栈:首先判空
bool PopStack(LinkStack* stack, SElemType* value){
if (StackEmpty(*stack)){
return false;
}
*value = (*stack)->data; //将栈顶数据域元素赋值给value
StackNode* temp = *stack; //创建一个temp指针,并将其指向 stack 指针所指向的内存地址,以便找到出栈位置并释放。
*stack = (*stack)->next; //令栈顶指针指向下一位结点,即更新栈顶指针
free(temp); //释放temp所指向的空间,即出栈元素所占的内存空间,temp本身会在函数结束后自动销毁。
return true;
}
//取栈顶元素
SElemType GetTop(LinkStack stack){
if (!StackEmpty(stack)){ //若栈不为空 //返回栈顶元素
return stack->data;
}
return NULL;
}
//遍历输出栈元素
bool StackPrint(LinkStack stack){
if (stack != NULL){
StackNode* temp = stack; //创建一个指针与stack指向同一位置
printf("出栈顺序:");
while (temp != NULL){
printf("%d ",temp->data);
temp = temp->next; //temp向下移动一位
}
printf("\n");
return true;
}
printf("栈为空\n");
return false;
}
//销毁链栈,释放内存
void DestroyStack(LinkStack* stack){
if (StackEmpty(*stack)) return;
StackNode* current = *stack;
while (current != NULL){
StackNode* q = current; //创建一个临时指针q,指向current所指向的内存地址
current = q->next; //更新栈顶指针
free(q); //释放q所指向的空间,即释放出栈元素所占的内存空间
}
free(current); //释放临时指针
*stack = NULL;
}当前栈的元素个数:4 栈顶元素:4 出栈顺序:4 3 2 1 出栈一次,栈顶元素为:4 出栈顺序:3 2 1 栈已被销毁释放 销毁栈后遍历栈结果: 栈为空
#2.3 括号匹配-C
// 栈:括号匹配
// c语言实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define MaxSize 50 //定义栈中元素最大个数
typedef char ElemType; //定义栈中元素类型为char
// 顺序栈 C
// top从0开始
typedef struct{
char data[MaxSize];
int top;
}SqStack;
void InitStack(SqStack* S);
bool StackEmpty(SqStack S);
bool StackFull(SqStack S);
bool Push(SqStack* S,ElemType x);
bool Pop(SqStack* S,ElemType *x);
//匹配算法
bool bracketCheck(ElemType str[], int length){
SqStack S;
InitStack(&S); //初始化栈
for(int i=0; i<length; i++){
//扫描到左括号就入栈
if(str[i]=='(' || str[i]=='{' || str[i]=='['){
Push(&S,str[i]);
}
else{
//扫描到右括号并且当前栈为空,即右括号单身情况
if(StackEmpty(S)){
return false; //匹配失败
}
ElemType topElem; //用来保存弹出栈的栈顶元素
Pop(&S, &topElem); //栈顶元素出栈
if(str[i]==')' && topElem!='('){
return false;
}
if(str[i]=='}' && topElem!='{'){
return false;
}
if(str[i]==']' && topElem!='['){
return false;
}
}
}
//扫描完整个字符串,如果栈不为空,说明左括号单身
return StackEmpty(S);
}
// ---------------------------------------------------------------
void test(){
SqStack S;
InitStack(&S);
Push(&S,'a');
Push(&S,'b');
Push(&S,'(');
ElemType x='1';
printf("栈中元素个数为:%d\n",S.top);
while (!StackEmpty(S)){
Pop(&S, &x);
printf("%c ",x);
}
}
int main(){
char str[MaxSize];
printf("请输入需要判断的括号:\n");
scanf("%s",str);
int len = strlen(str);
printf("当前输入的括号个数为:%d\n",len);
printf("--------现在开始进行判断--------\n");
if(bracketCheck(str,len)){
printf("匹配成功!");
}else{
printf("匹配失败!");
}
return 0;
}
// -------------------------------------------------------------
//初始化栈
void InitStack(SqStack* S){
S->top = 0;
}
//判断栈是否为空
bool StackEmpty(SqStack S){
if(S.top == 0){
return true;
}
return false;
}
//判断栈满
bool StackFull(SqStack S){
if (S.top == MaxSize){
printf("栈满\n");
return true;
}
else
return false;
}
//入栈
bool Push(SqStack* S,ElemType x){
if(StackFull(*S)){
return false;
}
S->data[S->top] = x;
S->top += 1;
}
//出栈,用x返回
bool Pop(SqStack* S,ElemType* x){
if(StackEmpty(*S)){
return false;
}
S->top -= 1;
*x = S->data[S->top];
}请输入需要判断的括号: (()()) 当前输入的括号个数为:6 --------现在开始进行判断-------- 匹配成功!
#2.4 表达式求值-C
evaluation of expression
表达式求值(最详细分析+代码实现+表达式之间的相互转换)-CSDN博客
中缀转换为后缀
// 栈:表达式求值(evaluation of expression)
// c语言实现
// 中缀 转换为 后缀,然后求值
//本程序只能处理有关运算符+、-、*、/的中缀表达式,不能是÷或者×及其他运算
//界限符只能是英文状态的左右括号即'('、')',操作数只能是整数
//本程序不会检查输入的中缀表达式是否正确,因此请您核验好自己的式子是否正确
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc、free的头文件
#include <string.h> //strlen的头文件,用于判断字符串长度
#include <math.h>
#define MaxSize 100
#define ElemType char
#define ElemType2 int
// 顺序栈 C
// top从0开始
typedef struct{
char data[MaxSize];
int top;
}StackChar; // 运算符栈
typedef struct{
char data[MaxSize];
int top;
}StackNumber; // 运算数栈
// -----------StackChar 运算符栈----------------------------------
bool InitChar(StackChar* stack); //初始化栈
bool EmptyChar(StackChar stack);//判断是否为空
bool PushChar(StackChar* stack, ElemType value);//入栈
bool PopChar(StackChar* stack, ElemType* value);//出栈
// ----------StackNumber 运算数栈----------------------------------
bool InitNum(StackNumber* stack); //初始化栈
bool EmptyNum(StackNumber stack);//判断是否为空
bool PushNum(StackNumber* stack, ElemType2 value);//入栈
bool PopNum(StackNumber* stack, ElemType2* value);//出栈
ElemType2 GetTop(StackNumber stack);//获取栈顶的元素
char* infixToSuffix(char* expression);//中缀转后缀
int eval_RPN(char* RPN);//后缀表达式求值
int main() {
char a[] = "1+2*(3-4)-5/5";
char *b = infixToSuffix(a);
printf("suffix:%s\n", b);
int c = eval_RPN(b);
printf("\nresult:%d\n", c);
return 0;
}
//----------------------------------------------------------------------
// 后缀表达式(逆波兰式RPN)运算函数
int eval_RPN(char* RPN) {
int i = 0;
StackNumber SS;
InitNum(&SS);
int num1, num2;
printf("每一次栈顶的元素:");
while (RPN[i] != '\0') //防止字符串走到末尾
{
if (RPN[i] != ' ') //跳过空格
{
if (RPN[i] >= '0' && RPN[i] <= '9') { //是数字
PushNum(&SS, (int)(RPN[i] - '0')); //将字符串转化为数字
}
else {
PopNum(&SS, &num2);
PopNum(&SS, &num1);
switch (RPN[i]) {
case '+': {
num1 += num2;
break;
}
case '-': {
num1 -= num2;
break;
}
case '*': {
num1 *= num2;
break;
}
case '/': {
num1 /= num2;
break;
}
}
PushNum(&SS, num1);
}
}
printf("%d ", GetTop(SS));
i++;
}
PopNum(&SS, &num1); //最后的结果
return num1;
}
// // 中缀转换前缀
// char* infixToPrefix(char* ){
// }
// 中缀转换后缀
char* infixToSuffix(char* expression){
int size = strlen(expression);
char temp, infix[size], *suffix; //静态数组infix、suffix分别存放要转换的中缀表达式和转换后的后缀表达式,字符变量temp存放弹出的栈顶元素
suffix = (char*)malloc(size*sizeof(char)); //为后缀表达式分配空间
strcpy(infix, expression); //将expression赋值给infix
StackChar S; //初始化一个栈,用于保存括号和暂时还不能确定运算顺序的运算符
InitChar(&S);
int i,j; //i用于遍历中缀表达式,j用于遍历后缀表达式
for(i=j=0; i<size; i++)
{
//如果是数字,直接放入后缀表达式
if(infix[i]>=48 && infix[i]<=57) //若当前字符是数字,字符0-9的ACSII码范围是[48,57]
{
suffix[j++] = infix[i];
if(infix[i+1]=='+' || infix[i+1]=='-' || infix[i+1]=='*' || infix[i+1]=='/') //若下一个字符是运算符,即+、-、*、/,则b加一个空格,以免不同的操作数混在一起
suffix[j++]=' ';
}
//如果是界限符(
else if(infix[i]=='(')
PushChar(&S, infix[i]); //若当前字符是左括号则直接入栈
//如果是界限符)
else if(infix[i]==')'){ //若当前字符是右括号
while(!EmptyChar(S)){ //栈非空,则不断弹出栈内字符并加入后缀表达式
PopChar(&S, &temp);
if(temp=='(') //直到弹出左括号停止,注意这个(不加入后缀表达式
break;
suffix[j++]=temp;
suffix[j++]=' '; //加一个空格,从而将字符隔开
}
}
//如果是运算符
else switch(infix[i])
{
case '*': case '/':{
while(!EmptyChar(S)){ //若栈非空,则弹出栈中优先级高于或等于当前运算符的所有运算符,并将这些运算符加入后缀表达式
PopChar(&S, &temp);
if(temp=='/' || temp=='*'){
suffix[j++]=temp;
suffix[j++]=' '; //加一个空格,从而将字符隔开
}
else if(temp=='(' || temp=='-' || temp=='+'){//若栈顶元素是左括号或者是优先级低于当前字符的运算符,则将栈顶元素入栈
PushChar(&S, temp);
break;
}
}
PushChar(&S, infix[i]); //把当前字符入栈
break;
}
case '-': case '+':{
while(!EmptyChar(S)){ //若栈非空,则弹出栈中优先级高于或等于当前运算符的所有运算符,并将这些运算符加入后缀表达式
PopChar(&S, &temp);
if(temp=='('){//若栈顶元素是左括号,则将栈顶元素入栈
PushChar(&S,temp);
break;
}
else if(temp=='/' || temp=='*' || temp=='-' || temp=='+'){
suffix[j++]=temp;
suffix[j++]=' '; //加一个空格,从而将字符隔开
}
}
PushChar(&S, infix[i]); //把当前字符入栈
break;
}
}
}
// 最后没有新字符,则把栈中所有元素弹出
while(!EmptyChar(S)){ //栈非空时依次弹出栈顶元素并加入后缀表达式
PopChar(&S, &temp);
suffix[j++]=temp;
suffix[j++]=' '; //加一个空格,从而将字符隔开
}
printf("结果是:\n");
for(i=0; i<j; i++) //j是数组中下一个可以插入元素的位置下标,因此suffix中存放字符的索引区间为[0,j-1]
printf("%c", suffix[i]); //输出b中的元素
printf("\n");
return suffix;
}
// -----------StackChar 运算符栈----------------------------------
//初始化顺序栈
//初始化有两种方式:top=-1 或者 top=0
bool InitChar(StackChar* stack){
stack->top = -1; //初始化栈顶指针的位置为-1
return true;
}
//判断栈空
bool EmptyChar(StackChar stack){
if (stack.top == -1)
return true;
else
return false;
}
/**
* @brief 入栈:
* 开辟一个新的空间,栈顶+1,然后将数据存入stack.data[stack.top]所在的位置.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool PushChar(StackChar* stack, ElemType value){
// if (StackFull(*stack)){
// return false;
// }
//若栈未满,执行入栈操作
stack->top++; //栈顶自增1
stack->data[stack->top] = value; //以栈顶位置作为下标存储数据
return true;
}
/**
* @brief 出栈:
* 读取栈顶stack.data[stack.top]的元素,然后使栈顶-1.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool PopChar(StackChar* stack, ElemType* value){
if(EmptyChar(*stack)){
return false;
}
*value = stack->data[stack->top]; //以栈顶位置作为下标的值赋值给value返回
stack->top--; //栈顶自减1
return true;
}
// ----------StackNumber 运算数栈----------------------------------
//初始化顺序栈
//初始化有两种方式:top=-1 或者 top=0
bool InitNum(StackNumber* stack){
stack->top = -1; //初始化栈顶指针的位置为-1
return true;
}
//判断栈空
bool EmptyNum(StackNumber stack){
if (stack.top == -1)
return true;
else
return false;
}
/**
* @brief 入栈:
* 开辟一个新的空间,栈顶+1,然后将数据存入stack.data[stack.top]所在的位置.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool PushNum(StackNumber* stack, ElemType2 value){
// if (StackFull(*stack)){
// return false;
// }
//若栈未满,执行入栈操作
stack->top++; //栈顶自增1
stack->data[stack->top] = value; //以栈顶位置作为下标存储数据
return true;
}
/**
* @brief 出栈:
* 读取栈顶stack.data[stack.top]的元素,然后使栈顶-1.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool PopNum(StackNumber* stack, ElemType2* value){
if(EmptyNum(*stack)){
return false;
}
*value = stack->data[stack->top]; //以栈顶位置作为下标的值赋值给value返回
stack->top--; //栈顶自减1
return true;
}
//读取栈顶元素
ElemType2 GetTop(StackNumber stack){
if(EmptyNum(stack)){
printf("栈为空");
return false;
}
return stack.data[stack.top];
}
结果是: 1 2 3 4- * + 5 5/ - suffix:1 2 3 4- * + 5 5/ - 每一次栈顶的元素:1 1 2 2 3 3 4 -1 -1 -2 -2 -1 -1 5 5 5 1 1 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 result:-2
#2.5进制转换
#C++
//c++实现进制转换
#include<iostream>
using namespace std;
#define MaxSize 100 //栈最大可以存放的元素个数
typedef char ElemType; //顺序栈存储的数据类型、代替ElemType
//创建顺序栈
typedef struct
{
ElemType* data; //栈底指针
int top; //栈顶的位置 如 0、1、2、3、4....MaxSize
} SqStack; //顺序栈的结构体定义
bool InitStack(SqStack& stack); //初始化栈
bool StackEmpty(SqStack stack);//判断是否为空
bool StackFull(SqStack stack); //判断是否已满
int GetStackSize(SqStack& stack);//获取顺序栈中元素个数
bool Push(SqStack& stack, ElemType value);//入栈
bool Pop(SqStack& stack, ElemType& value);//出栈
bool Pop(SqStack& stack); //出栈重载
bool GetTop(SqStack& stack, ElemType& value);//获取栈顶的元素
void DestroyStack(SqStack& stack);//销毁栈、释放栈的内存
//---------------------------------------------------------------------
// 十进制 -> 二进制
void DTB(int num, SqStack& Stack)
{
char num_b[64]; //用来存放转换出来的二进制
int i = 0;
while(num) { //2进制,入栈
// !!!注意:将余数转化为字符,用 +'0'
Push(Stack, num%2+'0'); //余数放入栈
num /= 2;
}
while(!StackEmpty(Stack)) { //出栈,直到栈为空
Pop(Stack, num_b[i++]);
}
cout<<"该数对应的二进制数为:"<<num_b<<endl;
}
// 八进制
void DTO(int num, SqStack& Stack)
{
char num_o[32]; //用来存放转换出来的八进制
char* temp = num_o;
while(num) { //入栈
Push(Stack, num%8+'0');
num /= 8;
}
while(!StackEmpty(Stack)) { //出栈 直到栈为空
Pop(Stack, *temp);
temp++; //下一个位置
}
*temp = '\0';
printf("该数对应的八进制数为:%s\n",num_o);
}
// 十六进制
void DTH(int num, SqStack& Stack)
{
char num_h[12] ; //用来存放转换出来的16进制
char* temp = num_h;
char top_num;
while(num) //入栈
{
Push(Stack,num%16);
num /= 16;
}
while(!StackEmpty(Stack)) //出栈 直到栈为空
{
Pop(Stack, top_num);
if((int)top_num > 9)
*temp = top_num - 10 + 'A';
else
*temp = top_num + '0';
temp++;
}
*temp = '\0';
printf("该数对应的十六进制数为:%s\n",num_h);
}
int main()
{
SqStack stack; //创建顺序栈
InitStack(stack); //初始化顺序栈
int value = 12;
printf("十进制数:%d\n",value);
DTB(value,stack);
DTO(value,stack);
DTH(value,stack);
//释放栈的内存
DestroyStack(stack);
return 0;
}
//---------------------------------------------------------------------
//初始化顺序栈
bool InitStack(SqStack& stack){
//注意:这里使用new进行空间分配,所以在后面摧毁栈的时候需要delete释放空间
//动态分配一个ElemType类型MaxSize长度的空间,将地址给顺序栈Stack的栈底指针
stack.data = new ElemType[MaxSize];
//判断顺序栈的栈底指针(stack.data)是否为空,若无地址,则分配失败
if(!stack.data){
return false;
}
stack.top = -1; //初始化栈顶指针的位置为-1
return true;
}
//判断栈空
bool StackEmpty(SqStack stack){
if (stack.top == -1)
return true;
else
return false;
}
//判断栈满
bool StackFull(SqStack stack){
if (stack.top == MaxSize-1) //top的位置值等于MaxSize-1时栈满,因为是从0开始的
return true;
else
return false;
}
//顺序栈中元素个数
int GetStackSize(SqStack& stack){
return stack.top+1; //栈顶位置即top的数值,就是栈中元素的个数
}
/**
* @brief 顺序栈入栈:
* 开辟一个新的空间,栈顶+1,然后将数据存入stack.data
[stack.top]所在的位置.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Push(SqStack& stack, ElemType value){
if (StackFull(stack)){
cout<<"栈满"<<endl;
return false;
}
//若栈未满,执行入栈操作
stack.top++; //栈顶自增1
stack.data[stack.top] = value; //以栈顶位置作为下标存储数据
return true;
}
/**
* @brief 顺序栈出栈:
* 读取栈顶stack.data
[stack.top]的元素,然后使栈顶-1.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Pop(SqStack& stack, ElemType &value){
if (StackEmpty(stack)){
cout<<"栈为空"<<endl;
return false;
}
value = stack.data[stack.top]; //以栈顶位置作为下标的值赋值给value返回
stack.top--; //栈顶自减1
return true;
}
// 重载
bool Pop(SqStack& stack){
if (StackEmpty(stack)){
cout<<"栈为空"<<endl;
return false;
}
stack.top--; //栈顶自减1
return true;
}
//读取栈顶元素
bool GetTop(SqStack& stack, ElemType &value){
if (StackEmpty(stack)){
cout<<"栈为空"<<endl;
return false;
}
value = stack.data[stack.top];
return true;
}
//销毁栈、释放栈的内存
void DestroyStack(SqStack& stack){
if(stack.data) { //若栈底指针分配有地址,则释放
delete stack.data; //释放栈底指针的地址
stack.top = -1; //令栈顶位置为0
stack.data= NULL; //将栈底指针指向空
cout<<"栈已释放内存!"<<endl;
}
}十进制数:12 该数对应的二进制数为:1100 该数对应的八进制数为:14 该数对应的十六进制数为:C 栈已释放内存!
#C
// 栈:进制转换
// c语言实现
#include<stdio.h>
#include<stdbool.h> //引入bool数据类型
#include <assert.h> //引入断言
#define MaxSize 50 //栈最大可以存放的元素个数
typedef char ElemType; //顺序栈存储的数据类型、用int代替ElemType
//创建顺序栈
// c
// top从-1开始
typedef struct
{
ElemType data[MaxSize]; //栈底指针
int top; //栈顶的位置 如 0、1、2、3、4....MaxSize
} SqStack; //顺序栈的结构体定义
bool InitStack(SqStack* stack); //初始化栈
bool StackEmpty(SqStack stack);//判断是否为空
bool StackFull(SqStack stack); //判断是否已满
int GetStackSize(SqStack stack);//获取顺序栈中元素个数
bool Push(SqStack* stack, ElemType value);//入栈
bool Pop(SqStack* stack, ElemType* value);//出栈
ElemType GetTop(SqStack stack);//获取栈顶的元素
//------------------------------------------------------
// 十进制 -> 二进制
// 对十进制数进行取余之后放入栈中,再出栈(倒序)就是目标结果。
void DTB(int num, SqStack* Stack)
{
char num_b[64]; //用来存放转换出来的二进制
int i = 0;
while(num) { //2进制,入栈
// !!!注意:将余数转化为字符,用 +'0'
Push(Stack, num%2 + '0'); //余数放入栈
num /= 2;
}
while(!StackEmpty(*Stack)) { //出栈,直到栈为空
Pop(Stack, &num_b[i++]);
}
printf("该数对应的二进制数为:%s\n",num_b);
}
// -> 八进制
void DTO(int num, SqStack* Stack)
{
char num_o[32]; //用来存放转换出来的八进制
char* temp = num_o; //用来记录当前一个位置
while(num) { //入栈
Push(Stack, num%8+'0');
num /= 8;
}
while(!StackEmpty(*Stack)) { //出栈,直到栈为空
Pop(Stack, temp);
temp++; //下一个位置
}
*temp = '\0';
printf("该数对应的八进制数为:%s\n",num_o);
}
// -> 十六进制
void DTH(int num, SqStack* Stack)
{
char num_h[12] ; //用来存放转换出来的16进制
char* temp = num_h;
char top_num; //栈顶元素
while(num) //入栈
{
Push(Stack,num%16);
num /= 16;
}
while(!StackEmpty(*Stack)) //出栈,直到栈为空
{
Pop(Stack, &top_num);
if((int)top_num > 9)
*temp = top_num - 10 + 'A';
else
*temp = top_num + '0';
temp++;
}
*temp = '\0';
printf("该数对应的十六进制数为:%s\n",num_h);
}
int main()
{
SqStack stack; //创建顺序栈
InitStack(&stack); //初始化顺序栈
int value = 12;
printf("十进制数:%d\n",value);
DTB(value, &stack);
DTO(value, &stack);
DTH(value, &stack);
return 0;
}
//----------------------------------------------------------------------
//初始化顺序栈
//初始化有两种方式:top=-1 或者 top=0
bool InitStack(SqStack* stack){
assert(stack); // 这里一定要断言,如果是空指针的话,就无法找到整个数组
stack->top = -1; //初始化栈顶指针的位置为-1
return true;
}
//判断栈空
bool StackEmpty(SqStack stack){
if (stack.top == -1)
return true;
else
return false;
}
//判断栈满
bool StackFull(SqStack stack){
//top的位置值等于MaxSize时栈满,因为数据是从0开始的
if (stack.top == MaxSize-1){
printf("栈满\n");
return true;
}
else
return false;
}
//顺序栈中元素个数
int GetStackSize(SqStack stack){
return stack.top+1; //栈顶位置即top的数值,就是栈中元素的个数
}
/**
* @brief 入栈:
* 开辟一个新的空间,栈顶+1,然后将数据存入stack.data[stack.top]所在的位置.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Push(SqStack* stack, ElemType value){
if (StackFull(*stack)){
return false;
}
//若栈未满,执行入栈操作
stack->top++; //栈顶自增1
stack->data[stack->top] = value; //以栈顶位置作为下标存储数据
return true;
}
/**
* @brief 出栈:
* 读取栈顶stack.data[stack.top]的元素,然后使栈顶-1.
*
* @param stack
* @param value
* @return true
* @return false
*/
bool Pop(SqStack* stack, ElemType* value){
if(StackEmpty(*stack)){
return false;
}
*value = stack->data[stack->top]; //以栈顶位置作为下标的值赋值给value返回
stack->top--; //栈顶自减1
return true;
}
//读取栈顶元素
ElemType GetTop(SqStack stack){
if(StackEmpty(stack)){
printf("栈为空");
return false;
}
return stack.data[stack.top];
}
//销毁栈、释放栈的内存
// 顺序栈是在声明栈时直接分配内存,并没有使用`malloc`函数,所以不需要手动`free`,函数运行结束后系统自动回收空间。
十进制数:12 该数对应的二进制数为:1100 该数对应的八进制数为:14 该数对应的十六进制数为:C
#3.队列Queue
#3.1 循环队列-CPP
#include<iostream>
using namespace std;
#define MaxSize 50 //最大队列长度
typedef int ElemType;
// 循环队列
// c++
typedef struct
{
ElemType* data; //初始化的动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue; //普通类型用 '.' *SqQueue指针类型用 '->'
bool InitQueue(SqQueue& Q); //循环队列初始化
int QueueLength(SqQueue Q); //循环队列长度
bool QueueEmpty(SqQueue Q); //判断队列是否为空
bool QueueFull(SqQueue Q); //判断队列是否已满
bool EnQueue(SqQueue& Q, ElemType value); //循环队列入队
bool DeQueue(SqQueue& Q, ElemType& value); //循环队列出队
bool QueuePrint(SqQueue Q); //打印输出队列
ElemType GetHead(SqQueue Q); //获取队头元素
int main()
{
SqQueue Q; //创建循环队列Q
InitQueue(Q); //队列初始化
ElemType value = -1;
int number = 0; //入队的元素个数
cout<<"请输入要入队的元素个数:"<<" ";
cin>>number;
int num = 0; //入队的数据元素
while( (number--) > 0){
EnQueue(Q, num); //将num入队
num++;
}
cout << "队列输出顺序:";
QueuePrint(Q); //遍历输出队列元素
cout << "队头元素为:" << GetHead(Q) << endl;
cout << "队列长度为:" << QueueLength(Q) << endl;
cout << "---出队一个元素后---" << endl;
DeQueue(Q, value);
cout << "出队元素为:" << value << endl;
QueuePrint(Q);
cout << "出队后队头元素为:" << GetHead(Q) << endl;
cout << "出队后队列长度为:" << QueueLength(Q) << endl;
delete Q.data; //释放存储空间
return 0;
}
//初始化队列,构造一个空队列Q。
bool InitQueue(SqQueue& Q){
Q.data = new ElemType[MaxSize]; //分配数组空间
//Q.data = (ElemType*)MaxSize * sizeof(ElemType); C语言语法
if (!Q.data){
return false; //存储分配失败
}
Q.front = Q.rear = 0; //头指针尾指针置为0, 队列为空
return true;
}
//求循环队列的长度/元素个数
int QueueLength(SqQueue Q){
//若rear指向大于队列长度后重新转一圈指向队头,假如rear=3,front= 4, 3-4=-1不合法。则通过(3-4+6)%6 = 5个元素
return ((Q.rear - Q.front + MaxSize) % MaxSize);
}
//判断队列是否为空。队空条件:队头指针==队尾指针
bool QueueEmpty(SqQueue Q){
return (Q.front == Q.rear);
}
//判断队列是否已满
bool QueueFull(SqQueue Q){
return (Q.rear + 1) % MaxSize == Q.front; //队列满
}
//入队:若循环队列Q未满,将value加入,使之成为新的队尾。
bool EnQueue(SqQueue& Q, ElemType value){
if (!QueueFull(Q)){ //如果队列没满
Q.data[Q.rear] = value; //将入队元素放入Q.rear所指向的空间中,,插入队尾
Q.rear = (Q.rear + 1) % MaxSize; //队尾指针+1.队尾指针后移,但不能一直+1会溢出,所以这里需要模运算,取(Q.rear+1)%MaxSize的余数
return true;
}
return false; //队列已满,入队失败
}
//出队:若循环队列Q非空,删除队头元素,并用value返回。
bool DeQueue(SqQueue& Q, ElemType &value){
if (!QueueEmpty(Q)){ //如果队列非空
value = Q.data[Q.front]; //将出栈元素保存到value中
Q.front = (Q.front + 1) % MaxSize; //队头指针+1
return true;
}
return false; //队列为空,出队失败
}
//取队头元素
ElemType GetHead(SqQueue Q){
if (!QueueEmpty(Q)){ //队列不为空
return Q.data[Q.front]; //返回队头指针元素
}
return false;
}
//遍历打印队列元素
bool QueuePrint(SqQueue Q) {
//这里注意!!! 如果要用这种方法,参数列表一定不能用'SqQueue &value'引用,
//因为使用&会修改真正的Q.front和Q.rear空间地址,影响原函数的指针位置,和后面的函数调用。
if (Q.front == Q.rear) //空队列
return false;
while (Q.front != Q.rear){
cout << Q.data[Q.front] << " ";
Q.front = (Q.front + 1) % MaxSize;
}
cout<<endl;
return true;
//方案二,由于额外创建了临时变量用来遍历队列,不管用不用'&Q'都不会影响原队列中front与rear的位置
//if (!QueueEmpty(Q)) //队列非空
//{
// ElemType temp = Q.front; //创建临时变量,位置与队头相同
// while (temp != Q.rear)
// {
// cout << Q.data[temp] << " "; //输出temp所处位置的元素
// temp = (temp + 1) % MaxSize; //temp位置上移加 1
// }
// cout << endl;
// return true;
//}
//return false;
}#3.1 循环队列-C
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#define MaxSize 50 //最大队列长度
typedef int ElemType;
// 循环队列
// C
typedef struct
{
ElemType data[MaxSize]; //初始化的动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue; //普通类型用 '.' *SqQueue指针类型用 '->'
bool InitQueue(SqQueue* Q); //循环队列初始化
int QueueLength(SqQueue Q); //循环队列长度
bool QueueEmpty(SqQueue Q); //判断队列是否为空
bool QueueFull(SqQueue Q); //判断队列是否已满
bool EnQueue(SqQueue* Q, ElemType value); //循环队列入队
bool DeQueue(SqQueue* Q, ElemType* value); //循环队列出队
ElemType GetHead(SqQueue Q); //获取队头元素
bool QueuePrint(SqQueue Q); //打印输出队列
int main()
{
SqQueue Q; //创建循环队列Q
InitQueue(&Q); //队列初始化
ElemType value = -1;
int number = 0; //入队的元素个数
printf("请输入要入队的元素个数:");
scanf("%d", &number);
int num = 0; //入队的数据元素
while( (number--) > 0){
EnQueue(&Q, num); //将num入队
num++;
}
printf("队列输出顺序:");
QueuePrint(Q); //遍历输出队列元素
printf("队头元素为:%d\n",GetHead(Q));
printf("队列长度为:%d\n",QueueLength(Q));
printf("---出队一个元素后---\n");
DeQueue(&Q, &value);
printf("出队元素为:%d\n",value);
QueuePrint(Q);
printf("出队后队头元素为:%d\n",GetHead(Q));
printf("出队后队列长度为:%d\n",QueueLength(Q));
return 0;
}
//初始化队列,构造一个空队列Q。
bool InitQueue(SqQueue* Q){
Q->front = Q->rear = 0; //头指针尾指针置为0, 队列为空
return true;
}
//求循环队列的长度/元素个数
int QueueLength(SqQueue Q){
//若rear指向大于队列长度后重新转一圈指向队头,假如rear=3,front= 4, 3-4=-1不合法。则通过(3-4+6)%6 = 5个元素
return ((Q.rear - Q.front + MaxSize) % MaxSize);
}
//判断队列是否为空。队空条件:队头指针==队尾指针
bool QueueEmpty(SqQueue Q){
return (Q.front == Q.rear);
}
//判断队列是否已满
bool QueueFull(SqQueue Q){
return (Q.rear + 1) % MaxSize == Q.front; //队列满
}
//入队:若循环队列Q未满,将value加入,使之成为新的队尾。
bool EnQueue(SqQueue* Q, ElemType value){
if (!QueueFull(*Q)){ //如果队列没满
Q->data[Q->rear] = value; //将入队元素放入Q->rear所指向的空间中,,插入队尾
Q->rear = (Q->rear + 1) % MaxSize; //队尾指针+1->队尾指针后移,但不能一直+1会溢出,所以这里需要模运算,取(Q->rear+1)%MaxSize的余数
return true;
}
return false; //队列已满,入队失败
}
//出队:若循环队列Q非空,删除队头元素,并用value返回。
bool DeQueue(SqQueue* Q, ElemType *value){
if (!QueueEmpty(*Q)){ //如果队列非空
*value = Q->data[Q->front]; //将出栈元素保存到value中
Q->front = (Q->front + 1) % MaxSize; //队头指针+1
return true;
}
return false; //队列为空,出队失败
}
//取队头元素
ElemType GetHead(SqQueue Q){
if (!QueueEmpty(Q)){ //队列不为空
return Q.data[Q.front]; //返回队头指针元素
}
return false;
}
//遍历打印队列元素
bool QueuePrint(SqQueue Q) {
if (!QueueEmpty(Q)) //队列非空
{
ElemType temp = Q.front; //创建临时变量,位置与队头相同
while (temp != Q.rear)
{
printf("%d ", Q.data[temp]);//输出temp所处位置的元素
temp = (temp + 1) % MaxSize; //temp位置上移加 1
}
printf("\n");
return true;
}
return false;
}请输入要入队的元素个数:3 队列输出顺序:0 1 2 队头元素为:0 队列长度为:3 ---出队一个元素后--- 出队元素为:0 1 2 出队后队头元素为:1 出队后队列长度为:2
#3.2 链队列-CPP
#include<iostream>
using namespace std;
#define MaxSize 50 //最大队列长度
typedef int QElemType;
// 链队列(带头结点)
// C++
//结点结构
typedef struct LinkNode
{
QElemType data;
struct LinkNode* next;
}LinkNode; //队列结点类型
//链队列,队头,对尾结构
//因为队列只处理队头、队尾,所以后面只操作这个结构体
typedef struct
{
LinkNode *front; //队头指针
LinkNode *rear; //队尾指针
}LinkQueue; //链式队列定义
bool InitQueue(LinkQueue& Q); //循环队列初始化
bool QueueEmpty(LinkQueue Q); //判断队列是否为空
int QueueLength(LinkQueue Q); //循环队列长度
bool EnQueue(LinkQueue& Q, QElemType value); //循环队列入队
bool DeQueue(LinkQueue& Q, QElemType& value); //循环队列出队
QElemType GetHead(LinkQueue Q); //获取队头元素
bool QueuePrint(LinkQueue Q); //打印输出队列
void DestroyQueue(LinkQueue& Q); //销毁链队列
int main()
{
LinkQueue Q;
QElemType value;
InitQueue(Q);
int number = 0; //入队的元素个数
cout << "请输入要入队的元素个数:" << " ";
cin >> number;
int num = 0; //入队的数据元素
while ((number--) > 0){
EnQueue(Q, num); //将num入队
num++;
}
cout << "队列输出顺序:";
QueuePrint(Q); //遍历输出队列元素
cout << "队头元素为:" << GetHead(Q) << endl;
cout << "队列长度为:" << QueueLength(Q) << endl;
DeQueue(Q, value); //出队
cout << "出队元素为:" <<value<<endl;
QueuePrint(Q); //遍历队列元素
DestroyQueue(Q);//销毁链式队列,释放内存空间
return 0;
}
//初始化链队列:front与rear都指向队头结点,队头结点next指针置空
bool InitQueue(LinkQueue& Q) {
// 将队列的头尾指针都指向同一个节点,表示队列为空
Q.front = Q.rear = new LinkNode;
//Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
if(!Q.front){
return false; //内存分配失败
}
// 带头结点
Q.front->next = NULL; //将队列队头指针置空
return true; //初始化完成
}
//链队列判空
bool QueueEmpty(LinkQueue Q) {
//队头队尾指针指向同一位置(队头结点),队列为空。
return Q.front == Q.rear;
}
//入队
// 将元素value入队:创建新结点,将元素放入结点数据域、新节点next指针置空、队尾rear指向新结点、更新队尾结点。
bool EnQueue(LinkQueue& Q, QElemType value) {
// step1创建指针型LinkNode结点,指针指向要插入的结点元素
LinkNode* temp = new LinkNode;
// step2判断是否分配成功
if (!temp) return false; //内存分配失败
// step3构建新结点
temp->data = value; //将要插入的元素放入temp结点数据域
temp->next = NULL; //temp结点指针域置空
// step4将新结点temp插入到队尾
Q.rear->next = temp; //将队尾指针接上temp结点
Q.rear = temp; //更新队尾结点
return true;
}
//出队:删除队头结点的下一位,头结点不存储数据元素。
// 判断链队列是否为空,创建temp指针指向要出栈的元素、删除该结点,将头结点指向删除结点的后继结点,更新队头,若删除的是队尾,则队头队尾指针均指向队头结点。
bool DeQueue(LinkQueue& Q, QElemType &value) {
// step1判断链队列是否为空
if (!QueueEmpty(Q)) //若链队列不为空
{
// step2创建temp指针指向要出栈的元素
LinkNode* temp = Q.front->next;//temp指向队头结点下一位即第一位元素
if (!temp) return false;
// step3删除该结点,将头结点指向删除结点的后继结点,更新队头
value = temp->data; //将temp所指结点的数据保存到value中
Q.front->next = temp->next;//更新队头结点
// step4若删除的是队尾,则队头队尾指针均指向队头结点
if (Q.rear == temp)//如果删除的是最后一个结点(尾结点),尾结点回移
{
Q.rear = Q.front;//rear、front均指向仅存的头结点
Q.front->next = NULL;
}
// step5释放出元素所占结点空间
delete temp; //释放出栈元素所占结点空间
return true; //出栈成功
}
return false; //队列为空
}
//获取链队的队头元素
QElemType GetHead(LinkQueue Q) {
if (!QueueEmpty(Q)){
return Q.front->next->data;
}
return false;
}
//链队列的长度/元素个数
// 这里Q不能用'&'引用型传递,否则下方队头指针front的移动会修改原队列front指针。不加引用,就会创建一个副本执行操作,故相比前者会多消耗些内存和时间。也可以创建一个临时指针temp对队列进行遍历,这样即使Q加了&, 也不会影响原链队列。
int QueueLength(LinkQueue Q) {
if (!QueueEmpty(Q)){
int count = 0; //元素个数/队列长度
while (Q.front != Q.rear)//直到 == 队尾rear
{
Q.front = Q.front->next;//队头指针后移一位
count++; //计数加一
}
return count;
}
return false; //队列为空或不存在
}
//遍历输出链队元素
bool QueuePrint(LinkQueue Q) {
if (!QueueEmpty(Q))
{
while (Q.front != Q.rear)
{
Q.front = Q.front->next; //将链队头指针指向第一个元素结点
cout << Q.front->data <<" "; //输出该结点所指的结点数据
}
cout << endl;
return true;
}
cout << "队列为空或不存在!";
return false;
}
//链队列销毁:从队头结点开始,一次释放所有结点
void DestroyQueue(LinkQueue& Q) {
LinkNode* temp; //创建临时指针
while (Q.front){
//反正rear指针闲置无事,此处可以不额外创建temp,直接将下列temp替换成Q.rear效果一样。
temp = Q.front->next; //temp指向队头结点的下一个结点
delete Q.front; //释放队头结点
Q.front = temp; //更新队头结点
}
Q.rear = NULL;
cout << "队列销毁成功!" << endl;
}#3.2 链队列-C
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#define MaxSize 50 //最大队列长度
typedef int ElemType;
// 链队列(带头结点)
// C
//结点结构
typedef struct LinkNode
{
ElemType data;
struct LinkNode* next;
}LinkNode; //队列结点类型
//链队列,队头,对尾结构
//因为队列只处理队头、队尾,所以后面只操作这个结构体
typedef struct
{
LinkNode *front; //队头指针
LinkNode *rear; //队尾指针
}LinkQueue; //链式队列定义
bool InitQueue(LinkQueue* Q); //循环队列初始化
bool QueueEmpty(LinkQueue Q); //判断队列是否为空
int QueueLength(LinkQueue Q); //循环队列长度
bool EnQueue(LinkQueue* Q, ElemType value); //循环队列入队
bool DeQueue(LinkQueue* Q, ElemType* value); //循环队列出队
ElemType GetHead(LinkQueue Q); //获取队头元素
bool QueuePrint(LinkQueue Q); //打印输出队列
void DestroyQueue(LinkQueue* Q); //销毁链队列
int main()
{
LinkQueue Q;
ElemType value;
InitQueue(&Q);
int number = 0; //入队的元素个数
printf("请输入要入队的元素个数: ");
scanf("%d", &number);
int num = 0; //入队的数据元素
while ((number--) > 0){
EnQueue(&Q, num); //将num入队
num++;
}
printf("入队完成!队列顺序:");
QueuePrint(Q); //遍历输出队列元素
printf("队头元素为:%d\n", GetHead(Q));
printf("队列长度为:%d\n", QueueLength(Q));
DeQueue(&Q, &value); //出队
printf("出队元素为:%d\n出队一个元素后,队列顺序:", value);
QueuePrint(Q); //遍历队列元素
DestroyQueue(&Q);//销毁链式队列,释放内存空间
return 0;
}
//初始化链队列:front与rear都指向队头结点,队头结点next指针置空
bool InitQueue(LinkQueue* Q) {
// 将队列的头尾指针都指向同一个节点,表示队列为空
Q->front = Q->rear = (LinkNode *)malloc(sizeof(LinkNode));
if(!Q->front){
return false; //内存分配失败
}
// 带头结点
Q->front->next = NULL; //将队列队头指针置空
return true; //初始化完成
}
//链队列判空
bool QueueEmpty(LinkQueue Q) {
//队头队尾指针指向同一位置(队头结点),队列为空。
return Q.front == Q.rear;
}
//入队
// 将元素value入队:创建新结点,将元素放入结点数据域、新节点next指针置空、队尾rear指向新结点、更新队尾结点。
bool EnQueue(LinkQueue* Q, ElemType value) {
// step1创建指针型LinkNode结点,指针指向要插入的结点元素
LinkNode* temp = (LinkNode*)malloc(sizeof(LinkNode));
// step2判断是否分配成功
if (!temp) return false; //内存分配失败
// step3构建新结点
temp->data = value; //将要插入的元素放入temp结点数据域
temp->next = NULL; //temp结点指针域置空
// step4将新结点temp插入到队尾
Q->rear->next = temp; //将队尾指针接上temp结点
Q->rear = temp; //更新队尾结点
return true;
}
//出队:删除队头结点的下一位,头结点不存储数据元素。
// 判断链队列是否为空,创建temp指针指向要出栈的元素、删除该结点,将头结点指向删除结点的后继结点,更新队头,若删除的是队尾,则队头队尾指针均指向队头结点。
bool DeQueue(LinkQueue* Q, ElemType *value) {
// step1判断链队列是否为空
if (!QueueEmpty(*Q)) //若链队列不为空
{
// step2创建temp指针指向要出栈的元素
LinkNode* temp = Q->front->next;//temp指向队头结点下一位即第一位元素
if (!temp) return false;
// step3删除该结点,将头结点指向删除结点的后继结点,更新队头
*value = temp->data; //将temp所指结点的数据保存到value中
Q->front->next = temp->next;//更新队头结点
// step4若删除的是队尾,则队头队尾指针均指向队头结点
if (Q->rear == temp)//如果删除的是最后一个结点(尾结点),尾结点回移
{
Q->rear = Q->front;//rear、front均指向仅存的头结点
Q->front->next = NULL;
}
// step5释放出元素所占结点空间
free(temp); //释放出栈元素所占结点空间
return true; //出栈成功
}
return false; //队列为空
}
//获取链队的队头元素
ElemType GetHead(LinkQueue Q) {
if (!QueueEmpty(Q)){
return Q.front->next->data;
}
return false;
}
//链队列的长度/元素个数
// 这里Q不能用'*'引用型传递,否则下方队头指针front的移动会修改原队列front指针。不加引用,就会创建一个副本执行操作,故相比前者会多消耗些内存和时间。也可以创建一个临时指针temp对队列进行遍历,这样即使Q加了*, 也不会影响原链队列。
int QueueLength(LinkQueue Q) {
if (!QueueEmpty(Q)){
int count = 0; //元素个数/队列长度
while (Q.front != Q.rear)//直到 == 队尾rear
{
Q.front = Q.front->next;//队头指针后移一位
count++; //计数加一
}
return count;
}
return false; //队列为空或不存在
}
//遍历输出链队元素
bool QueuePrint(LinkQueue Q) {
if (!QueueEmpty(Q))
{
while (Q.front != Q.rear)
{
Q.front = Q.front->next; //将链队头指针指向第一个元素结点
printf("%d ", Q.front->data); //输出该结点所指的结点数据
}
printf("\n");
return true;
}
printf("队列为空或不存在!\n");
return false;
}
//链队列销毁:从队头结点开始,一次释放所有结点
void DestroyQueue(LinkQueue* Q) {
LinkNode* temp; //创建临时指针
while (Q->front){
//反正rear指针闲置无事,此处可以不额外创建temp,直接将下列temp替换成Q.rear效果一样。
temp = Q->front->next; //temp指向队头结点的下一个结点
free(Q->front); //释放队头结点
Q->front = temp; //更新队头结点
}
Q->rear = NULL;
printf("链队列销毁成功!\n");
}请输入要入队的元素个数: 4 入队完成!队列顺序:0 1 2 3 队头元素为:0 队列长度为:4 出队元素为:0 出队一个元素后,队列顺序:1 2 3 链队列销毁成功!
#4.串String
#4.1 顺序串-C
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define MaxSize 100
// 顺序存储 字符串
// C
// 定长存储
typedef struct str{
char ch[MaxSize+1]; //每个分量存储一个字符
int length; //串的实际长度
}SString;
SString initSString(); // 初始化
bool isEmpty(SString str); // 判空
bool strAssign(SString* str, char chars[]); // 赋值
void printSString(SString str); // 打印
SString strCopy(SString str1, SString str2); // 拷贝
SString strCat(SString str1, SString str2); // 连接
SString subString(SString str, int pos, int len); // 取子串
SString strDelete(SString str, int pos, int len); // 删除
SString strInsert(SString str, int pos, SString sub); // 插入
void clearSString(SString* str); // 清空
// SString strReplace(SString str, int pos, int len, SString sub);
int StrCompare(SString str1, SString str2); // 比较
int Index_BF(SString S, SString T);
void get_next(SString T, int *next); // 获取next数组
int Index_KMP(SString str, SString sub);
int main()
{
SString str1=initSString(), str2=initSString();
char s[100]="hello world";
strAssign(&str1,s);
printf("--1.strAssign赋值,打印字符串str1:\n");
printSString(str1);
printf("--2.拷贝字符串str1到字符串str2。");
str2 = strCopy(str2,str1);
printf("打印字符串str2:\n");
printSString(str2);
printf("--3.用str2返回部分str1:\n");
str2 = subString(str1,7,5);
//hello world中w是第7个
printSString(str2);
printf("--4.删除部分str1后的str1:\n");
str1 = strDelete(str1,7,5);
printSString(str1);
printf("--5.插入s2到str1,");
// 插入第6个位置,插入字符串s2
char s2[] ="suhan";
clearSString(&str2);
strAssign(&str2,s2);
str1 = strInsert(str1,6,str2);
printf("打印 插入str2后的字符串str1:\n");
printSString(str1);
printf("--6.连接str1到str2末尾.\n");
str2 = strCat(str2,str1);
printf("--7.对比str1和str2\nstr1: ");
printSString(str1);
printf("str2: ");
printSString(str2);
printf("str1和str2比较结果为:%d\n",StrCompare(str1,str2));
printf("----8.模式匹配:\n");
SString S = initSString();
char S_chars[] = "abcdefg";
strAssign(&S,S_chars);
SString T = initSString();
char T_chars[] = "cde";
strAssign(&T,T_chars);
printf("S: ");
printSString(S);
printf("T: ");
printSString(T);
printf("T在S中第一次出现的位置为(BF):%d\n",Index_BF(S,T));
printf("T在S中第一次出现的位置为(KMP):%d\n",Index_KMP(S,T));
return 0;
}
// --------------------------------------------------------------------------
//字符串初始化
SString initSString(){
SString str;
str.length=0;
return str;
}
//判断字符串是否为空
bool isEmpty(SString str){
return (str.length==0);
}
//字符串赋值:把串str赋值为chars。
bool strAssign(SString* str, char chars[]){
if (chars==NULL) return false;
int i=0;
while(chars[i]){
str->ch[++str->length] = chars[i];
i++;
}
return true;
}
//打印字符串
void printSString(SString str)
{
int i;
for(i=1; i<=str.length; i++)
printf("%c",str.ch[i]);
printf("\n");
}
//字符串复制:把str2赋值给str1
SString strCopy(SString str1, SString str2)
{
int i;
for(i=1; i<=str2.length; i++){
str1.ch[i] = str2.ch[i];
}
str1.length = str2.length;
return str1;
}
//字符串连接:把str2连接到str1的末尾
SString strCat(SString str1, SString str2)
{
SString str3 = initSString();
int i;
for(i=1; i<=str1.length; i++)
str3.ch[i] = str1.ch[i];
for(i=1; i<=str2.length; i++)
str3.ch[i+str1.length] = str2.ch[i];
str3.length = str1.length + str2.length;
return str3;
}
//求子串:返回串str的第pos个字符起长度为len的子串。
SString subString(SString str, int pos, int len)
{
//判断子串范围越界
if(pos<1 || pos > str.length || len+pos-1 > str.length){
printf("输入位置错误!\n");
exit(0);
}
int i;
//构建新串
SString subStr = initSString();
for(i=1; i <= len; i++){
subStr.ch[i] = str.ch[i + pos-1];
}
subStr.length = len;
return subStr;
}
//删除指定位置的字符
SString strDelete(SString str, int pos, int len)
{
if(pos<1 || pos>str.length || len+pos-1>str.length){
printf("删除位置错误!\n");
exit(0);
}
int i;
//把pos位置之后的内容依次往前移动len个位置
for(i=pos+len; i<=str.length; i++){
str.ch[i-len] = str.ch[i];
}
str.length = str.length - len;
return str;
}
//插入:在串str1的pos位置插入串str2
SString strInsert(SString str1, int pos, SString str2)
{
if(pos<1 || pos>str1.length){
printf("插入位置错误!\n");
exit(0);
}
int i;
//把即将存放str2的位置预留出来,str1扩容
for(i=pos; i <= str1.length; i++){
str1.ch[i + str2.length] = str1.ch[i];
}
//把str2插入到预留的位置
for(i=1; i <= str2.length; i++){
str1.ch[i + pos-1] = str2.ch[i];
}
str1.length = str2.length + str1.length;
return str1;
}
// 清空串
void clearSString(SString* str)
{
str->length = 0;
}
// 比较操作。
//若str1>str2,则返回值>0。
//若str1=str2,则返回值=0。
//若str1<str2,则返回值<0。
int StrCompare(SString str1, SString str2)
{
for (int i=1; i<=str1.length && i<=str2.length; i++){
if (str1.ch[i] != str2.ch[i])
return str1.ch[i]-str2.ch[i];
}
//扫描过的所有字符都相同,则长度长的串更大
return str1.length - str2.length;
}
// 模式匹配(定位字串)
// 定位子串。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0。
// 朴素模式匹配算法(BF)
int Index_BF(SString S, SString T)
{
int i=1, j=1; //i是主串位置,j是模式串位置
//匹配逻辑
while(i <= S.length && j <= T.length){
if(S.ch[i] == T.ch[j]){
++i; ++j; //继续比较后继字符
}else{
//指针后退重新开始匹配
i = i-j+2; //i-j=0,+2表示在原有的位置的下一个
j = 1;
}
}
//匹配完成判断
if(j > T.length){ //匹配成功
return i - T.length;
}else{
return 0;
}
}
void get_next(SString T, int *next)
{
int i = 1, j = 0;
next[1] = 0;
while (i < T.length){
if(j==0 || T.ch[i]==T.ch[j]){ //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符
++i; ++j;
next[i] = j; //若pi = pj, 则next[j+1] = next[j] + 1
}else{
j = next[j]; //否则令j = next[j],j值回溯,循环继续
}
}
}
int Index_KMP(SString S, SString T)
{
int i=1, j=1;
int next[255]; //定义next数组
get_next(T, next); //得到next数组
//输出next数组
printf("next数组为:");
for(int i=1; i<=T.length; i++){
printf("%d ", next[i]);
}
printf("\n");
//匹配逻辑
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i] == T.ch[j]){ //字符相等则继续。next[1]=0使得j=0
++i; ++j;
}else{
j = next[j]; //模式串向右移动,i不变
}
}
//匹配完成判断
if(j>T.length){
return i-T.length; //匹配成功
}else{
return 0;
}
}
--1.strAssign赋值,打印字符串str1: hello world --2.拷贝字符串str1到字符串str2。打印字符串str2: hello world --3.用str2返回部分str1: world --4.删除部分str1后的str1: hello --5.插入s2到str1,打印插入str2后的字符串str1: hellosuhan --6.连接str1到str2末尾. --7.对比str1和str2 str1: hellosuhan str2: suhanhellosuhan str1和str2比较结果为:-11 ----8.模式匹配: S: abcdefg T: cde T在S中第一次出现的位置为(BF):3 next数组为:0 1 1 T在S中第一次出现的位置为(KMP):3
#4.2 朴素模式匹配算法(BF)

int Index(SString S, SString T){
int i=1, j=1; //i是主串位置,j是模式串位置
//匹配逻辑
while(i <= S.length && j <= T.length){
if(S.ch[i] == T.ch[j]){
++i; ++j; //继续比较后继字符
}else{
//指针后退重新开始匹配
i = i-j+2; //i-j=0,+2表示在原有的位置的下一个
j = 1;
}
}
//匹配完成判断
if(j > T.length){ //匹配成功
return i - T.length;
}else{
return 0;
}
}#4.3 KMP模式匹配算法
如果主串的已匹配过的前缀序列中,有某个后缀正好是模式串的前缀,那么就可以将模式向后滑动到与这些相等字符对齐的位置。主串 i 指针无须回溯,并继续从该位置开始进行比较。而模式向后滑动位数的计算仅与模式本身的结构有关,与主串无关。
当第 j 个字符匹配失败,由前 1~j-1 个字符(j 前面的不包含本身所有字符组成的串)组成的串记为 s1(模式前缀)则:
next[j]=s1本身的最长相等的′前后缀′长度+1特别地,next[1]=0。
科学的推导得出以下公式:
next[j]=⎩⎨⎧0,max{k∣1<k<j且′p1...pk−1′=′pj−k+1...pj−1′},即max{k∣1<k<j且模式串的前缀=后缀}.1,当j=1时当此集合不为空其他情况(前后缀匹配失败,即s1=0)代码:
void get_next(SString T, int *next){
int i = 1, j = 0;
next[1] = 0;
while (i < T.length){
if(j==0 || T.ch[i]==T.ch[j]){ //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符
++i; ++j;
next[i] = j; //若pi = pj, 则next[j+1] = next[j] + 1
}else{
j = next[j]; //否则令j = next[j],j值回溯,循环继续
}
}
}
int Index_KMP(SString S, SString T){
int i=1, j=1;
int next[255]; //定义next数组
get_next(T, next); //得到next数组
//匹配逻辑
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i] == T.ch[j]){ //字符相等则继续。next[1]=0使得j=0
++i; ++j;
}else{
j = next[j]; //模式串向右移动,i不变
}
}
//匹配完成判断
if(j>T.length){
return i-T.length; //匹配成功
}else{
return 0;
}
}#4.4 KMP进一步优化
KMP进一步优化,优化的是next数组为nextval数组。
当 j 所在的字符 == next[ j ]所在的字符,那么如果 j 所在的字符匹配失败,next[ j ]所在的字符必然也会匹配失败。所以 next[ j ] 可以直接等于next[ next[ j ] ],即直接等于 j 的next的next,减少了一次运算。更新后的数组命名为nextval。
void get_nextval(String T, int *nextval){
int i = 1, j = 0;
nextval[1] = 0;
while (i < T.length){
if(j==0 || T.ch[i]==T.ch[j]){ //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符
++i; ++j;
if(T.ch[i] != T.ch[j]){ //若当前字符与前缀字符不同
nextval[i] = j; //则当前的j为nextval在i位置的值
}else{
//如果与前缀字符相同
//则将前缀字符的nextval值给nextval在i位置上的值
nextval[i] = nextval[j];
}
}else{
j = nextval[j]; //否则令j = next[j],j值回溯,循环继续
}
}
}#树形结构
#1.双亲表示法
| data | parent |
|---|
//树的双亲表示法结点结构定义
#define MAX_TREE_SIZE 100
typedef int TElemType; //树结点的数据类型,目前暂定为整型
//结点结构
typedef struct PTNode{
TElemType data; //结点数据
int parent; //双亲位置
}PTNode;
//树结构
typedef struct{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int n; //结点数
}PTree;#2.孩子表示法
把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表。


//树的孩子表示法结构定义
#define MAX_TREE_SIZE 100
/*孩子结点*/
typedef struct CTNode{
int child;
struct CTNode *next;
}*ChildPtr;
/*表头结点*/
typedef struct{
TElemType data;
ChildPtr firstchild;
}CTBox;
/*树结构*/
typedef struct{
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int n; //结点数
}#3.孩子兄弟表示法
把一棵复杂的树变成了一棵二叉树。
| data | firstchild | rightsib |
|---|

/*树的孩子兄弟表示法结构定义*/
typedef struct CSNode{
TElemtype data;
struct CSNode *firstchild, *rightsib;
} CSNode, *CSTree;#4.二叉树Binary tree
- 顺序存储
- 链式存储
- 二叉链表
- 三叉链表
- 线索二叉树(线索链表)
#4.1 顺序存储
二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下、自左至右(完全二叉树层次结构)存储完全二叉树上的结点元素,即将完全二叉树上编号为 i 的结点元素存储在一维数组下标为 i-1 的分量中。
/* 二叉树
顺序存储(完全二叉树)
C语言实现
物理结构:
头结点不存储
^ a b c ^ d ...
0 1 2 3 4 5 ...
*/
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<stdbool.h>
#define MaxSize 1024
typedef char ElemType;
typedef struct
{
ElemType data[MaxSize]; // 存储树结点的数组
int BiTreeNum; // 二叉树的结点个数
}SqBiTree;
void InitBiTree(SqBiTree *T); // 初始化
void CreatBiTree(SqBiTree *T, int n); // 创建树
bool SetRightChild(SqBiTree *T,int i,int num); // 设置右孩子
bool SetLeftChild(SqBiTree *T,int i,int num); // 设置左孩子
void DestroyBiTree(SqBiTree *T); // 销毁二叉树
int GetParent(SqBiTree T,int i); // 获取父结点
int GetParentOfNum(SqBiTree T, int num); // 找到某个值的父结点的下标
ElemType GetRoot(SqBiTree T); // 获取根结点
int GetCountOfBiTree(SqBiTree T); // 获取树的结点数
int GetEdgesOfBiTree(SqBiTree T); // 获取树的边数
int GetFullNodesOfBiTree(SqBiTree T); // 获取树的满结点数(2度)
int GetHalfNodesOfBiTree(SqBiTree T); // 获取树的半结点数的下标(1度)
int GetLeavesOfBiTree(SqBiTree T); // 获取树的叶子数(0度)
int GetLeavesOfBiTree2(SqBiTree T);
int GetDepthOfBiTree(SqBiTree T); // 获取树的深度
void PrintBiTree(SqBiTree T); // 打印二叉树的结点
void PreOrder_Traverse(SqBiTree T,int n); //先序遍历二叉树
void InOrder_Traverse(SqBiTree T, int n); //中序遍历二叉树
void PostOrder_Traverse(SqBiTree T, int n); //后序遍历二叉树
void Level_Traverse(SqBiTree T, int n); //层序遍历二叉树
int main()
{
SqBiTree T;
InitBiTree(&T);
printf("请输入根结点(输入#表示该结点为空):");
CreatBiTree(&T,1);
printf("打印二叉树:");
PrintBiTree(T);
printf("\n");
printf("根结点: %c\n",GetRoot(T));
printf("结点数: %d\n", GetCountOfBiTree(T));
printf("边数: %d\n", GetEdgesOfBiTree(T));
printf("深度: %d\n",GetDepthOfBiTree(T));
printf("满结点数n2: %d\n",GetFullNodesOfBiTree(T));
printf("半结点n1的位置: %d\n",GetHalfNodesOfBiTree(T));
printf("叶子数n0: %d\n",GetLeavesOfBiTree(T));
printf("叶子数n0(way2): %d\n",GetLeavesOfBiTree2(T));
printf("打印二叉树:\n");
PrintBiTree(T);
printf("\n");
printf("先序遍历:");
PreOrder_Traverse(T,1);
printf("\n");
printf("中序遍历:");
InOrder_Traverse(T,1);
printf("\n");
printf("后序遍历:");
PostOrder_Traverse(T,1);
printf("\n");
printf("层序遍历:");
Level_Traverse(T, 1);
printf("\n");
printf("销毁二叉树后打印二叉树:\n");
DestroyBiTree(&T);
PrintBiTree(T);
return 0;
}
// -------------------------------------------------------------------
// 初始化,制空
void InitBiTree(SqBiTree *T)
{
int i;
for(i=0; i<MaxSize; i++){ // 清除所用内存空间的杂乱数据
T->data[i] = '\0';
}
T->BiTreeNum = 0;
return;
}
// 创建二叉树,前序递归方式
void CreatBiTree(SqBiTree *T, int n)
{
char ch;
fflush(stdin); // 清除标准输入流中的内容
scanf("%c",&ch);
if(ch == '#'){
return;
}
else{
T->data[n] = ch;
T->BiTreeNum++;
printf("%c的左子树:",ch);
CreatBiTree(T, 2*n);
printf("%c的右子树:",ch);
CreatBiTree(T, (2*n+1));
}
}
// 设置左孩子
bool SetLeftChild(SqBiTree *T, int i, int num){
if( (2*i) >= MaxSize)
return false;
T->data[2*i] = num;
return true;
}
// 设置右孩子
bool SetRightChild(SqBiTree *T, int i, int num){
if( (2*i + 1) >= MaxSize)
return false;
T->data[2*i+1] = num;
return true;
}
void DestroyBiTree(SqBiTree *T){
T->BiTreeNum = 0;
}
// ------------------------ Get 方法 ----------------------------
// 获取某个结点的父结点
int GetParent(SqBiTree T, int i){
if(T.data[i] = '\0')
return 0;
return i/2;
}
// 获取某个值的父结点
int GetParentOfNum(SqBiTree T, int num){
for(int i=0; i <= MaxSize; i++){
if(T.data[i]==num)
return i/2;
}
}
// 获取根结点
ElemType GetRoot(SqBiTree T){
return T.data[1];
}
/*
总结点数n
n = n2 + n1 + n0
n = n1 + 2*n2 + 1(树的结点个数=所有结点的度数+1)
↓
n0 = n2 + 1
*/
// 获取树的结点个数
int GetCountOfBiTree(SqBiTree T){
if(T.BiTreeNum == 0)
return 0;
return T.BiTreeNum;
}
// 获取边的个数
// 任意一棵树,若结点数量为 n,则边的数量为 n−1。
int GetEdgesOfBiTree(SqBiTree T){
if(T.BiTreeNum == 0)
return 0;
return T.BiTreeNum - 1;
}
// 获取树的满结点数(2度)
// 当n为偶数 n2 = n/2 - 1
// 当n为奇数时 n2 = n/2
int GetFullNodesOfBiTree(SqBiTree T){
if(T.BiTreeNum == 0)
return 0;
// 如果是偶数
if(T.BiTreeNum % 2 == 0)
return T.BiTreeNum/2 - 1;
// 如果是奇数
else if(T.BiTreeNum % 2 == 1)
return T.BiTreeNum/2;
}
// 获取树的半结点数的下标(1度)
// n1 只能为1或者0
int GetHalfNodesOfBiTree(SqBiTree T){
// 如果是奇数 或者 空树
if(T.BiTreeNum == 0 || T.BiTreeNum % 2 == 1)
return 0;
// 如果是偶数
if(T.BiTreeNum % 2 == 0)
return T.BiTreeNum/2;
}
// 获取树的叶子数(0度)
int GetLeavesOfBiTree(SqBiTree T){
if(T.BiTreeNum == 0)
return 0;
// 如果是偶数
if(T.BiTreeNum % 2 == 0)
return T.BiTreeNum/2;
// 如果是奇数
else if(T.BiTreeNum % 2 == 1)
return T.BiTreeNum/2 + 1;
}
// 推论性质2
int GetLeavesOfBiTree2(SqBiTree T){
if(T.BiTreeNum == 0)
return 0;
return GetFullNodesOfBiTree(T) + 1;
}
// 获取树的深度
// 任意一棵树,若结点数量为 n,则树的深度为 log2(n)+1。
int GetDepthOfBiTree(SqBiTree T){
if(T.BiTreeNum == 0)
return 0;
// 求log2(n) 向下取整 +1
return (int)log2(T.BiTreeNum)+1;
}
// 打印树
void PrintBiTree(SqBiTree T){
int i;
for(i=1; i<=T.BiTreeNum; i++){
if(T.data[i] != '\0')
printf("%3c",T.data[i]);
}
printf("\n");
}
// ------------------------ Traverse 遍历方法 ----------------------------
void PreOrder_Traverse(SqBiTree T, int n){
if(T.data[n] == '\0')
return;
else{
printf("%3c",T.data[n]);
PreOrder_Traverse(T, 2*n); // 左子树
PreOrder_Traverse(T, (2*n+1)); // 右子树
}
}
void InOrder_Traverse(SqBiTree T, int n){
if(T.data[n] == '\0')
return;
else{
InOrder_Traverse(T, 2*n); // 左子树
printf("%3c",T.data[n]);
InOrder_Traverse(T, (2*n+1)); // 右子树
}
}
void PostOrder_Traverse(SqBiTree T, int n){
if(T.data[n] == '\0')
return;
else{
PostOrder_Traverse(T, 2*n); // 左子树
PostOrder_Traverse(T, (2*n+1)); // 右子树
printf("%3c",T.data[n]);
}
}
// 层序遍历
// 顺序存储的二叉树层次遍历很简单
void Level_Traverse(SqBiTree T, int n){
int i;
for(i=n; i<=T.BiTreeNum; ++i)
printf("%3c",T.data[i]);
}
例如:
graph TB
%%左子树
1((a))---2((b))---4((d))---8((8h))
2---5((e))---10((10j))
4---9((9i))
5---11(( ))
%%右子树
1((a))---3((c))---6((f))
3---7((g))
%%隐藏结点
style 11 fill: #f100,stroke-width:0px
%%隐藏路径
linkStyle 6 stroke:#0ff,stroke-width:0px请输入根结点(输入#表示该结点为空):a a的左子树:b b的左子树:d d的左子树:h h的左子树:# h的右子树:# d的右子树:i i的左子树:# i的右子树:# b的右子树:e e的左子树:j j的左子树:# j的右子树:# e的右子树:# a的右子树:c c的左子树:f f的左子树:# f的右子树:# c的右子树:g g的左子树:# g的右子树:# 打印二叉树: a b c d e f g h i j
根结点: a 结点数: 10 边数: 9 深度: 4 满结点数n2: 4 半结点n1的位置: 5 叶子数n0: 5 叶子数n0(way2): 5 打印二叉树: a b c d e f g h i j
先序遍历: a b d h i e j c f g 中序遍历: h d i b j e a f c g 后序遍历: h i d j e b f g c a 层序遍历: a b c d e f g h i j 销毁二叉树后打印二叉树:
#4.2 二叉链表
二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。

/* 二叉树
链式存储:二叉链表
C语言实现
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef char ElemType;
// 链二叉树结构(二叉链表)-C
typedef struct BiNode
{
ElemType data;
struct BiNode *lchild;
struct BiNode *rchild;
}BiNode, *BiTree;
BiTree CreatBiTree(); //前序遍历递归法建立二叉树算法
BiTree CreatBiTreeByArray(ElemType data[], int *j, int length); //使用数组直接创建二叉树(前序)
void DestroyBiTree(BiTree T); //销毁二叉树
void PreOrderTraverse(BiTree T); //前序遍历递归二叉树算法
void InOrderTraverse(BiTree T); //中序遍历递归二叉树算法
void PostOrderTraverse(BiTree T); //后序遍历递归二叉树算法
void Layer_order(BiTree T); //按层遍历递归二叉树算法
int GetDepth(BiTree T); //求二叉树深度递归算法
int GetNodeCount(BiTree tree); //求二叉树结点数递归算法
int GetLeafCount(BiTree tree); //求二叉树叶子数递归算法
ElemType visit(BiTree T); //获取元素
// ------------------------ Queue 辅助队列 ----------------------------
//链队列结构-C
typedef struct LinkNode
{
BiNode* data;
struct LinkNode* next;
}LinkNode;
//链队列,队头,对尾结构
typedef struct
{
LinkNode *front; //队头指针
LinkNode *rear; //队尾指针
}LinkQueue; //链式队列定义
bool InitQueue(LinkQueue* Q); //循环队列初始化
bool QueueEmpty(LinkQueue Q); //判断队列是否为空
bool EnQueue(LinkQueue* Q, BiNode* value); //循环队列入队
BiNode* DeQueue(LinkQueue* Q); //循环队列出队
void DestroyQueue(LinkQueue* Q); //销毁链队列
//初始化链队列:front与rear都指向队头结点,队头结点next指针置空
bool InitQueue(LinkQueue* Q) {
// 将队列的头尾指针都指向同一个节点,表示队列为空
Q->front = Q->rear = (LinkNode *)malloc(sizeof(LinkNode));
if(!Q->front){
return false; //内存分配失败
}
// 带头结点
Q->front->next = NULL; //将队列队头指针置空
return true; //初始化完成
}
//链队列判空
bool QueueEmpty(LinkQueue Q) {
//队头队尾指针指向同一位置(队头结点),队列为空。
return Q.front == Q.rear;
}
//入队
// 将元素value入队:创建新结点,将元素放入结点数据域、新节点next指针置空、队尾rear指向新结点、更新队尾结点。
bool EnQueue(LinkQueue* Q, BiNode* value) {
// step1创建指针型LinkNode结点,指针指向要插入的结点元素
LinkNode* temp = (LinkNode*)malloc(sizeof(LinkNode));
// step2判断是否分配成功
if (!temp) return false; //内存分配失败
// step3构建新结点
temp->data = value; //将要插入的元素放入temp结点数据域
temp->next = NULL; //temp结点指针域置空
// step4将新结点temp插入到队尾
Q->rear->next = temp; //将队尾指针接上temp结点
Q->rear = temp; //更新队尾结点
return true;
}
//出队:删除队头结点的下一位,头结点不存储数据元素。
BiNode* DeQueue(LinkQueue* Q) {
// step1判断链队列是否为空
if (!QueueEmpty(*Q)) //若链队列不为空
{
// step2创建temp指针指向要出栈的元素
LinkNode* temp = Q->front->next;//temp指向队头结点下一位即第一位元素
if (!temp) return NULL;
// step3删除该结点,将头结点指向删除结点的后继结点,更新队头
BiNode* value = temp->data; //将temp所指结点的数据保存到value中
Q->front->next = temp->next;//更新队头结点
// step4若删除的是队尾,则队头队尾指针均指向队头结点
if (Q->rear == temp)//如果删除的是最后一个结点(尾结点),尾结点回移
{
Q->rear = Q->front;//rear、front均指向仅存的头结点
Q->front->next = NULL;
}
// step5释放出元素所占结点空间
free(temp); //释放出栈元素所占结点空间
return value; //出栈成功
}
return NULL; //队列为空
}
//链队列销毁:从队头结点开始,一次释放所有结点
void DestroyQueue(LinkQueue* Q) {
LinkNode* temp; //创建临时指针
while (Q->front){
//反正rear指针闲置无事,此处可以不额外创建temp,直接将下列temp替换成Q.rear效果一样。
temp = Q->front->next; //temp指向队头结点的下一个结点
free(Q->front); //释放队头结点
Q->front = temp; //更新队头结点
}
Q->rear = NULL;
printf("链队列销毁成功!\n");
}
/*
//示例二叉树的结构
A
/
B
/ \
C D
/ \
E F
\
G
*/
int main()
{
BiTree root;
// input输出创建二叉树
// printf("请输入根结点(输入#表示该结点为空):");
// root=CreatBiTree(); //创建树
// 数组创建二叉树
ElemType data[] = "abc##de#g##f####";
int i=0;
int length = sizeof(data) / sizeof(ElemType);
root = CreatBiTreeByArray(data, &i, length); //创建树
// printf("%c\n",visit(root->lchild->lchild));
printf("二叉树深度为:%d\n", GetDepth(root));
printf("二叉树结点数为:%d\n", GetNodeCount(root));
printf("二叉树叶子数为:%d\n", GetLeafCount(root));
printf("--- 二叉树按层遍历结果为 ---\n");
printf("前序遍历二叉树: \n");
PreOrderTraverse(root);
printf("\n");
printf("中序遍历二叉树: \n");
InOrderTraverse(root);
printf("\n");
printf("后序遍历二叉树: \n");
PostOrderTraverse(root);
printf("\n");
printf("按层遍历二叉树: \n");
Layer_order(root); //按层遍历树
printf("\n");
// 销毁二叉树
printf("销毁二叉树\n");
DestroyBiTree(root);
return 0;
}
// 前序遍历递归法建立二叉树算法
BiTree CreatBiTree(){
BiTree T;
ElemType data;
fflush(stdin);
// scanf("%c",&data);
data = getchar();
if(data == '#')
T = NULL;
else{
T = (BiTree)malloc(sizeof(BiNode));
T->data = data;
printf("%c的左子树:",data);
T->lchild = CreatBiTree();
printf("%c的右子树:",data);
T->rchild = CreatBiTree();
}
return T;
}
// 使用数组直接创建二叉树(前序)
// j指针指示当前到达的数组位置, 从数组0开始
BiTree CreatBiTreeByArray(ElemType data[], int *j, int length){
BiTree T;
if((*j) >= length || data[*j] == '#'){
T = NULL;
(*j)++;
}
else{
T = (BiTree)malloc(sizeof(BiNode));
T->data = data[*j];
(*j)++;
T->lchild = CreatBiTreeByArray(data, j, length);
T->rchild = CreatBiTreeByArray(data, j, length);
}
return T;
}
// 销毁二叉树
void DestroyBiTree(BiTree T){
if(T != NULL){
DestroyBiTree(T->lchild);
DestroyBiTree(T->rchild);
free(T);
}
}
// ------------------------ Traverse 遍历方法 ----------------------------
//前序遍历递归二叉树算法
void PreOrderTraverse(BiTree T){
if(T==NULL)
return;
printf("%3c", T->data); //显示结点数据,可以更改为其他对结点操作
PreOrderTraverse(T->lchild); //再先序遍历左子树
PreOrderTraverse(T->rchild); //最后先序遍历右子树
}
//中序遍历递归二叉树算法
void InOrderTraverse(BiTree T){
if(T==NULL)
return;
InOrderTraverse(T->lchild);
printf("%3c", T->data);
InOrderTraverse(T->rchild);
}
//后序遍历递归二叉树的算法
void PostOrderTraverse(BiTree T){
if(T==NULL)
return;
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
printf("%3c", T->data);
}
//按层遍历递归二叉树算法
// 每出队一个结点,就把它的孩子放入结点。
void Layer_order(BiTree T)
{
LinkQueue Q; //定义辅助队列
InitQueue(&Q); //初始化辅助队列
// 注意判断是不是NULL
if(T != NULL){
EnQueue(&Q, T); //将根节点入队
}
while(!QueueEmpty(Q)){ //队列不空则循环
BiNode* temp = DeQueue(&Q);
printf("%3c", visit(temp)); //访问出队结点
//两种判断是否为空结点
if(temp->lchild != NULL){
EnQueue(&Q, temp->lchild); //左子树不空,则左子树根节点入队
}
if(temp->rchild){
EnQueue(&Q, temp->rchild); //右子树不空,则右子树根节点入队
}
}
}
// ----------------------- Get 计算结点 ----------------------------
// 获取元素
ElemType visit(BiTree T){
if(T==NULL){
return '#';
}
return T->data;
}
//计算二叉树深度:递归求树的深度
int GetDepth(BiTree T){
if(T==NULL){
return 0;
} else {
int lchildDepth = GetDepth(T->lchild);
int rchildDepth = GetDepth(T->rchild);
//树的深度=Max(左子树深度,右子树深度)+1
if(lchildDepth > rchildDepth){
return lchildDepth+1;
} else {
return rchildDepth+1;
}
}
}
//计算二叉树结点总数
int GetNodeCount(BiTree tree){
if(tree==NULL){
return 0;
} else {
return GetNodeCount(tree->lchild) + GetNodeCount(tree->rchild) + 1;
}
}
//计算二叉树叶子结点数
int GetLeafCount(BiTree tree){
if(tree==NULL){
return 0;
} else if(tree->lchild==NULL && tree->rchild==NULL) {
return 1;
} else {
return GetLeafCount(tree->lchild) + GetLeafCount(tree->rchild);
}
}二叉树深度为:5 二叉树结点数为:7 二叉树叶子数为:3 --- 二叉树按层遍历结果为 --- 前序遍历二叉树: a b c d e g f 中序遍历二叉树: c b e g d f a 后序遍历二叉树: c g e f d b a 按层遍历二叉树: a b c d e f g 销毁二叉树
#例题:寻找二叉树中的最大值
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int ElemType;
// 链二叉树结构(二叉链表)-C
typedef struct BiNode
{
ElemType data;
struct BiNode *lchild;
struct BiNode *rchild;
}BiNode, *BiTree;
ElemType visit(BiTree T){
if(T==NULL){
return '#';
}
return T->data;
}
// 使用数组直接创建二叉树(前序)
// j指针指示当前到达的数组位置
BiTree CreatBiTreeByArray(ElemType data[], int *j, int length){
BiTree T;
if((*j) >= length || data[*j] == -1){
T = NULL;
(*j)++;
}
else{
T = (BiTree)malloc(sizeof(BiNode));
T->data = data[*j];
(*j)++;
T->lchild = CreatBiTreeByArray(data, j, length);
T->rchild = CreatBiTreeByArray(data, j, length);
}
return T;
}
//前序遍历递归二叉树算法
void PreOrderTraverse(BiTree T){
if(T==NULL)
return;
printf("%3d", T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
//计算二叉树中各结点中的最大元素的值
int GetMax(BiTree T) {
if (T==NULL)
return 0;
int maxl = GetMax(T->lchild);
int maxr = GetMax(T->rchild);
int max = maxl > maxr ? maxl : maxr;
return max > T->data ? max : T->data;
}
//way2,显示比较过程
int GetMax2(BiTree T) {
if(T==NULL)
return 0;
int max = T->data;
printf("%d ", T->data);
max = GetMax2(T->lchild)>max ? GetMax2(T->lchild) : max;
return GetMax2(T->rchild)>max ? GetMax2(T->rchild) : max;
}
int main()
{
BiTree root;
ElemType data[] = {31,23,66,-1,62,-1,-1,94,49,-1,-1,-1,12,5,88,-1,-1,-1,-1};
int i=0;
int length = sizeof(data) / sizeof(ElemType);
root = CreatBiTreeByArray(data, &i, length); //创建树
// printf("\n%d\n",visit(root->lchild->lchild));
printf("前序遍历二叉树: \n");
PreOrderTraverse(root);
printf("\n");
printf("最大值是:%d\n",GetMax(root));
return 0;
}#4.3 三叉链表
二叉链表这样找孩子结点很简单,但是找父节点很麻烦。所以再添加父结点指针*parent构成三叉链表。
//二叉树的结点(三叉链表)
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左、右孩子指针
struct BiTNode *parent; //父结点指针
}BiTNode,*BiTree;#4.4 线索二叉树
线索二叉树(线索链表)
利用二叉链表的空指针域来存放指向其前驱或后继的指针,这样就可以像遍历单链表那样方便地遍历二叉树。引入线索二叉树正是为了加快查找结点前驱和后继的速度。
我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。
| lchild | ltag | data | rtag | rchild |
|---|
其中,ltag, rtag初始化时,都为0。
- ltag==0时指向该结点的左孩子,为1时指向该结点的前驱。
- rtag==0时指向该结点的右孩子,为1时指向该结点的后继。

/* 二叉树
线索二叉树
C 实现(但是没有双指针,需要在c++中运行)
*/
#include<stdio.h>
#include<stdlib.h>
typedef char ElemType;
typedef struct ThreadNode {
ElemType data;
struct ThreadNode *lchild, *rchild;//左右孩子指针
//默认0代表左右孩子, 1代表前驱或者后继
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
ThreadTree CreatBiTree(); //前序遍历递归法建立二叉树算法
ThreadTree CreatBiTreeByArray(ElemType data[], int &j, int length); //使用数组直接创建二叉树(前序)
// 中序线索化
void InThread(ThreadNode* p, ThreadNode* &pre);
void CreateInThread(ThreadTree T);
// 前序线索化
void PreThread(ThreadNode* p, ThreadNode* &pre);
void CreatePreThread(ThreadTree T);
// 后序线索化
void PostThread(ThreadNode* p, ThreadNode* &pre);
void CreatePostThread(ThreadTree T);
void DestroyThreadTree(ThreadTree T); //销毁二叉树
// 中序线索遍历
ThreadNode* FirstNode(ThreadNode* p);
ThreadNode* Next(ThreadNode* p);
void InOrder(ThreadNode *T); //中序遍历
ThreadNode* LastNode(ThreadNode* p);
ThreadNode* Pre(ThreadNode* p);
void RevInOrder(ThreadNode *T); //中序倒叙遍历
void PreOrder(ThreadNode *T); // 前序线索遍历
// 后序线索遍历考试几乎会不考到,故省略。
void visit(ThreadTree T); //获取元素
/*
//示例二叉树的结构
A
/ \
B C
\ /
D E
*/
int main()
{
ThreadTree root;
// printf("请输入根结点(输入#表示该结点为空):");
// root=CreatBiTree(); //创建树
// 数组创建二叉树
ElemType data[] = "ab#d##ce###";
int i=0;
int length = sizeof(data) / sizeof(ElemType);
root = CreatBiTreeByArray(data, i, length); //创建树
// 中序线索化
CreateInThread(root);
printf("前序遍历二叉树: \n");
InOrder(root);
printf("\n");
printf("后序遍历二叉树: \n");
RevInOrder(root);
// // 前序线索化
// CreatePreThread(root);
// printf("前序遍历二叉树: \n");
// PreOrder(root);
// printf("\n");
// 销毁二叉树
printf("\n销毁二叉树\n");
DestroyThreadTree(root);
return 0;
}
//-------------------------------------------------------------
// 前序遍历递归法建立二叉树算法
ThreadTree CreatBiTree(){
ThreadTree T;
ElemType data;
fflush(stdin);
// scanf("%c",&data);
data = getchar();
if(data == '#')
T = NULL;
else{
T = (ThreadTree)malloc(sizeof(ThreadNode));
T->data = data;
T->ltag = 0;
T->rtag = 0;
printf("%c的左子树:",data);
T->lchild = CreatBiTree();
printf("%c的右子树:",data);
T->rchild = CreatBiTree();
}
return T;
}
// 使用数组直接创建二叉树(前序)
// j指针指示当前到达的数组位置, 从数组0开始
ThreadTree CreatBiTreeByArray(ElemType data[], int &j, int length){
ThreadTree T;
if(j >= length || data[j] == '#'){
T = NULL;
j++;
}
else{
T = (ThreadTree)malloc(sizeof(ThreadNode));
T->data = data[j];
T->ltag = 0;
T->rtag = 0;
j++;
T->lchild = CreatBiTreeByArray(data, j, length);
T->rchild = CreatBiTreeByArray(data, j, length);
}
return T;
}
// 销毁二叉树
void DestroyThreadTree(ThreadTree T){
if(T != NULL){
if(T->ltag == 0)
DestroyThreadTree(T->lchild);
if(T->rtag == 0)
DestroyThreadTree(T->rchild);
free(T);
}
}
// 获取元素
void visit(ThreadTree T){
if(T==NULL){
printf("# ");;
}
printf("%c ",T->data);
}
// --------------------------- Thread 线索化 ----------------------------
/*
设指针pre指向刚刚访问过的结点,指针p指向正在访问的结点,即pre指向p的前驱(p先走,然后再pre)。在中序遍历的过程中,
1. 检查p的左指针`lchild`是否为空,若为空就将它指向pre;
2. 检查pre的右指针`rchild`是否为空,若为空就将它指向p。
*/
// 中序线索化。一边遍历,一边线索化
// 后序线索化的代码和中序完全相同
void InThread(ThreadNode* p, ThreadNode* &pre)
{
if(p != NULL){
InThread(p->lchild, pre); //递归,线索化左子树
//---
//左子树为空,建立前驱线索
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
//右子树为空,建立前驱结点的后继线索
if(pre != NULL && pre->rchild == NULL) {
pre->rchild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点成为刚刚访问过的结点
//---
InThread(p->rchild, pre); //递归,线索化右子树
}
}
// 中序线索化
void CreateInThread(ThreadTree T){
ThreadNode *pre = NULL; //第一个结点没有前驱,这里的NULL会赋给第一个结点的前驱
if(T != NULL){
InThread(T, pre); //线索化二叉树
pre->rchild = NULL; //遍历结束后的最后一个结点没有后继
pre->rtag = 1;
}
}
// 前序线索化
// 先序线索化的代码和中序基本相同,但是有一点不同,当一个结点没有左孩子时候,它的左指针lchild会指向前驱,这时候,lchild所指的并不是左子树,而是左线索,所以会出现遍历错误。
void PreThread(ThreadNode* p, ThreadNode* &pre) // 前序线索化二叉树子函数
{
if(p != NULL){
//左子树为空,建立前驱线索
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
//右子树为空,建立前驱结点的后继线索
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
// 【注意】这里在递归入口处有条件限制,左、右指针不是线索才能继续递归
if(p->ltag == 0)
PreThread(p->lchild, pre); // 递归,左子树线索化
if(p->rtag == 0)
PreThread(p->rchild, pre); // 递归,右子树线索化
}
}
// 前序线索化
void CreatePreThread(ThreadTree T){ // 前序线索化二叉树
ThreadNode *pre = NULL;
if(T != NULL){
PreThread(T, pre);
pre->rchild = NULL; // 非空二叉树,线索化
pre->rtag = 1; // 后处理中序最后一个结点
}
}
// 后序线索化
// 后序线索化的代码和中序完全相同,只有顺序不同
void PostThread(ThreadNode* p, ThreadNode* &pre){ // 后序线索化二叉树子函数
if(p != NULL){
PostThread(p->lchild, pre); // 递归,左子树线索化
PostThread(p->rchild, pre); // 递归,右子树线索化
if(p->lchild == NULL){
// 建立当前节点的前驱线索
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){ // pre != NULL 排除第一个结点前驱为空的情况
// 建立当前节点的后继线索
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
}
// 后序线索化
void CreatePostThread(ThreadTree T){ // 前序线索化二叉树
ThreadNode *pre = NULL;
if(T != NULL){
PostThread(T, pre);
pre->rchild = NULL; // 非空二叉树,线索化
pre->rtag = 1; // 后处理中序最后一个结点
}
}
// ----------------------- Traverse 遍历方法 ----------------------------
// 中序顺序遍历
//找到以P为根的子树中,第一个被中序遍历的结点
ThreadNode* FirstNode(ThreadNode* p){
//循环找到右子树最左下结点(不一定是叶结点)
while(p->ltag == 0)
p=p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode* Next(ThreadNode* p){
//右子树中最左下结点
if(p->rtag == 0)
return FirstNode(p->rchild);
else
return p->rchild; //rtag==1直接返回后继线索
}
//对中序线索二叉树进行中序遍历(顺序)
// (利用线索实现的非递归算法)空间复杂度O(1)
void InOrder(ThreadNode *T){
for(ThreadNode *p=FirstNode(T); p!=NULL; p=Next(p))
visit(p);
}
// 中序倒叙遍历
//找到以P为根的子树中,最后一个被中序遍历的结点
ThreadNode* LastNode(ThreadNode* p){
//循环找到最右下结点(不一定是叶子结点)
while(p->rtag == 0)
p = p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的前驱结点
ThreadNode* Pre(ThreadNode* p){
//左子树中最右下结点
if(p->ltag == 0)
return LastNode(p->lchild);
else
return p->lchild;
}
//对中序线索二叉树进行中序遍历(倒叙)
// (利用线索实现的非递归算法)空间复杂度O(1)
void RevInOrder(ThreadNode *T){
for(ThreadNode *p=LastNode(T); p!=NULL; p=Pre(p))
visit(p);
}
// 前序遍历
// 因为二叉链表只能找前序后继,所以是顺序遍历(三叉链表才能找前驱)
void PreOrder(ThreadNode *T){
if(T != NULL){
ThreadNode *p = T;
while (p != NULL){
while (p->ltag == 0){ // 左指针不是线索,则边访问边左移
visit(p); // 访问结点
p = p->lchild; // 左移,访问左子树
}
visit(p); // 此时p左必为线索,但还没有被访问,则访问
p = p->rchild; // 此时p左孩子不存在,则右指针若非空,则不论是否为线索都指向其后继
}
}
}前序遍历二叉树: b d a e c 后序遍历二叉树: c e a d b 销毁二叉树
#5.哈夫曼树Huffman
哈夫曼树:在含有n个带权叶子节点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
/* 二叉树
哈夫曼树:在含有n个带权叶子节点的二叉树中,
其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
用一个 静态三叉链表 来存储
C++实现
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
typedef double DataType; //结点权值的数据类型
typedef struct HTNode //单个结点的信息
{
DataType weight; //权值
int parent; //父节点
int lc, rc; //左右孩子
} *HuffmanTree;
typedef char **HuffmanCode; //字符指针数组中存储的元素类型
void Select(HuffmanTree& HT, int n, int& min1, int& min2);
void Select2(HuffmanTree& HT, int n, int& s1, int& s2); //在哈夫曼树中选择两个权值最小的结点
void Select3(HuffmanTree& HT, int n, int& s1, int& s2);
void CreateHuff(HuffmanTree& HT, DataType* w, int n); //构建哈夫曼树
void PrintHuff(HuffmanTree HT, int n); //打印哈夫曼树
void HuffCoding(HuffmanTree& HT, HuffmanCode& HC, int n); //哈夫曼编码
double GetWpl(HuffmanTree HT, int n, int target);
double GetWPL(HuffmanTree HT, int n);
double GetAverageLength(HuffmanTree HT, HuffmanCode HC, int n);
double GetCompressionRate(HuffmanTree HT, HuffmanCode HC, int n);
int main()
{
//测试数据
int n = 1;
DataType w[] = {27,8,15,15,30,5};
// 获取长度
n = sizeof(w)/sizeof(DataType);
//创建哈夫曼树
HuffmanTree HT;
CreateHuff(HT, w, n);
//打印哈夫曼树
PrintHuff(HT, n);
HuffmanCode HC;
HuffCoding(HT, HC, n);
//打印哈夫曼编码
for (int i=1; i <= n; i++){
printf("%.2lf的哈夫曼编码是:%s\n", HT[i].weight, HC[i]);
}
//计算哈夫曼树的带权路径长度
printf("\n哈夫曼树的带权路径长度WPL是:%.2lf\n", GetWPL(HT,n));
// GetAverageLength(HC, n);
printf("压缩率是 %.2lf %%", GetCompressionRate(HT, HC, n)*100);
return 0;
}
// ------------------------- 哈夫曼树 构建------------------------
// 在下标为1到i-1的范围(n是叶子结点数)找到权值最小的两个值的下标, 其中s1的权值小于s2的权值, 返回s1和s2的下标
void Select2(HuffmanTree& HT, int n, int& s1, int& s2)
{
int min;
//找第一个最小值
//初始化,把第一个父结点为0的叶子结点作为最小值
for (int i=1; i <= n; i++){
if (HT[i].parent == 0){
min = i;
break;
}
}
//在剩下的n-1个父结点为0的叶子结点中,找到权值最小的
for (int i= min+1; i <= n; i++){
if (HT[i].parent == 0 && HT[i].weight < HT[min].weight)
min = i;
}
s1 = min; //第一个最小值给s1
//找第二个最小值
for (int i=1; i <= n; i++){
if (HT[i].parent == 0 && i != s1){
min = i;
break;
}
}
for (int i= min+1; i <= n; i++){
if (HT[i].parent == 0 && HT[i].weight < HT[min].weight && i != s1)
min = i;
}
s2 = min; //第二个最小值给s2
}
/*
设立两个变量,x(min1),y(min2)
将数组前两个值赋值给x,y;
比对x,y的大小,
更大的值给y,更小的值给x
循环数组,与y对比,当小于y时,与x对比,若小于x,则将x的值给y,x的值为min;
大于x则将min赋值给y;
*/
void Select(HuffmanTree& HT, int n, int& min1, int& min2)
{
//初始化,把第一个父结点为0的叶子结点作为最小值
for (int i=1; i <= n; i++){
if (HT[i].parent == 0){
min1 = i;
break;
}
}
for (int i=1; i <= n; i++){
if (HT[i].parent == 0 && i!=min1){
min2 = i;
break;
}
}
//min1比min2小
if (HT[min1].weight > HT[min2].weight){
int temp = min1;
min1 = min2;
min2 = temp;
}
for (int i=1; i <= n; i++){
if(HT[i].parent == 0){
if (HT[i].weight < HT[min1].weight){
min2 = min1;
min1 = i;
}else if (HT[i].weight < HT[min2].weight && i != min1){
min2 = i;
}
}
}
}
void Select3(HuffmanTree& HT, int n, int& s1, int& s2)
{
double min1=255, min2=255; //初始化,把第一个父结点为0的叶子结点作为最小值
s1=s1=0;
for (int i=1; i <= n; i++){
if(HT[i].parent == 0){
if (HT[i].weight < min1){
min2 = min1, s2 = s1;
min1 = HT[i].weight, s1 = i;
}else if (HT[i].weight < min2){
min2 = HT[i].weight, s2 = i;
}
}
}
}
// 构建哈夫曼树
// HT是哈夫曼树的根结点,w是n个叶子结点的权值数组,n是叶子结点(初始节点)的个数
void CreateHuff(HuffmanTree& HT, DataType* w, int n)
{
// step1.分配足够空间
int number = 2*n - 1; //哈夫曼树总结点数
HT = (HuffmanTree)calloc(number + 1, sizeof(HTNode)); //开m+1个HTNode,因为下标为0的HTNode不存储数据
if (!HT){
printf("分配内存失败\n");
exit(0);
}
// step2.构建叶子结点
for (int i=1; i <= n; i++){
HT[i].weight = w[i - 1]; //赋权值给n个叶子结点
}
// step3.构建哈夫曼树(分支节点),所以从新位置开始
for (int i= n+1; i <= number; i++){
//选择权值最小的s1和s2,生成它们的父结点
int s1, s2;
Select(HT, i-1, s1, s2); //在下标为1到i-1的范围找到权值最小的两个值的下标,其中s1的权值小于s2的权值
HT[i].weight = HT[s1].weight + HT[s2].weight; //i的权重是s1和s2的权重之和
HT[s1].parent = i; //s1的父亲是i
HT[s2].parent = i; //s2的父亲是i
HT[i].lc = s1; //左孩子是s1
HT[i].rc = s2; //右孩子是s2
}
}
//打印哈夫曼树中各结点之间的关系
void PrintHuff(HuffmanTree HT, int n)
{
int m = 2*n-1;
printf("哈夫曼树为:>\n");
printf("下标 权值 父结点 左孩子 右孩子\n");
printf("0 \n");
for (int i=1; i <= m; i++){
printf("%-4d %-6.2lf %-6d %-6d %-6d\n", i, HT[i].weight, HT[i].parent, HT[i].lc, HT[i].rc);
}
printf("\n");
}
// ------------------------- 哈夫曼 编码------------------------
//生成哈夫曼编码
void HuffCoding(HuffmanTree& HT, HuffmanCode& HC, int n)
{
HC = (HuffmanCode)malloc(sizeof(char*) * (n+1)); //开n+1个空间char**,因为下标为0的空间不用
char* code = (char*)malloc(sizeof(char) * n); //辅助空间,编码最长为n(最长时,前n-1个用于存储数据,最后1个用于存放'\0')
code[n - 1] = '\0'; //辅助空间最后一个位置为'\0'
for (int i = 1; i <= n; i++)
{
int start = n - 1; //每次生成数据的哈夫曼编码之前,先将start指针指向'\0'
int c = i; //正在进行的第i个数据的编码
int parent_c = HT[c].parent; //找到该数据的父结点
while (parent_c) //直到父结点为0,即父结点为根结点时,停止
{
//如果该结点是其父结点的左孩子,则编码为0,否则为1
if (HT[parent_c].lc == c)
code[--start] = '0';
else
code[--start] = '1';
c = parent_c; //继续往上进行编码
parent_c = HT[c].parent; //c的父结点
}
HC[i] = (char*)malloc(sizeof(char) * (n-start)); //开辟用于存储编码的内存空间
strcpy(HC[i], &code[start]); //将编码拷贝到字符指针数组中的相应位置
}
free(code); //释放辅助空间
}
// --------------------------- Get -----------------------------
// 结点的带权路径长度
// 获得n个叶子情况下结点target的带权路径长度
// 结点的带权路径长度=权×边数
double GetWpl(HuffmanTree HT, int n, int target){
if(target <= 0 || target > n){
return -1;
}
int sum = 0; //边数
int parent_target = HT[target].parent;
while(parent_target){
sum++;
parent_target = HT[parent_target].parent;
}
return sum * HT[target].weight;
}
// 树的带权路径长度WPL
double GetWPL(HuffmanTree HT, int n){
double wpl = 0;
for (int i=1; i <= n; i++){
wpl += GetWpl(HT, n, i);
}
return wpl;
}
// 计算哈夫曼编码的平均长度(字符位数)
double GetAverageLength(HuffmanTree HT, HuffmanCode HC, int n){
double Number_of_AVEdigits = 0; //压缩后的平均长度(字符位数)
//遍历哈夫曼编码
for (int i=1; i <= n; i++){
// printf("%s哈夫曼编码长度:%d\n", HC[i], strlen(HC[i]));
Number_of_AVEdigits += strlen(HC[i]) * HT[i].weight / 100;
}
return Number_of_AVEdigits;
}
// 计算压缩率
// 压缩率 = 加权平均字符位数 / 未压缩字符位数
double GetCompressionRate(HuffmanTree HT, HuffmanCode HC, int n){
double Number_of_digits = 0; //未压缩字符位数
for(int i=1; i <= n; i++){
if(pow(2, i) >= n){
Number_of_digits = i;
break;
}
}
GetAverageLength(HT, HC, n);
return (Number_of_digits - GetAverageLength(HT, HC, n) ) / Number_of_digits;
}
// 计算哈夫曼编码的熵
//pase哈夫曼树为:> 下标权值父结点左孩子右孩子 0 1 27.00 9 0 0 2 8.00 7 0 0 3 15.00 8 0 0 4 15.00 9 0 0 5 30.00 10 0 0 6 5.00 7 0 0 7 13.00 8 6 2 8 28.00 10 7 3 9 42.00 11 4 1 10 58.00 11 8 5 11 100.00 0 9 10
27.00的哈夫曼编码是:01 8.00的哈夫曼编码是:1001 15.00的哈夫曼编码是:101 15.00的哈夫曼编码是:00 30.00的哈夫曼编码是:11 5.00的哈夫曼编码是:1000
哈夫曼树的带权路径长度WPL是:241.00 压缩率是 19.67 %
【注意】calloc() 函数将分配的内存全部初始化为零。如果不需要初始化,可以使用 malloc() 函数代替。
#6.并查集Disjoint Set
#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
int UFsets[SIZE]; //集合元素数组
//初始化并查集,S相当于父结点,一开始都是一个节点,所以都是-1
void Initial(int S[]){
for (int i=0; i<SIZE; i++)
S[i]=-1;
}
//Find“查”操作优化,先找到根节点,再进行“压缩路径”
// 将查找路径上所有结点都挂到根结点下
int Find(int S[], int x){
int root = x;
while(S[root] >= 0)
root=S[root]; //循环找到根
//压缩路径
while(x != root){ //将查找路径上所有结点都挂到根结点下
int temp=S[x]; //temp指向x的父节点
S[x]=root; //把x直接挂到根节点下
x=temp; //继续操作x的父结点,准备也挂在root节点下
}
return root;//返回根节点编号
}
//Union“并”操作,将两个集合合并为一个(把一个树的根变成另一个树的根的孩子)
void Union(int S[], int x, int y){
int rootx=Find(S,x);
int rooty=Find(S,y);
//要求x与y是不同的集合
if(rootx == rooty){
return;
}
if(S[rootx] <= S[rooty]){ //x结点数更多(因为是负数)
S[rootx] += S[rooty]; //累加结点总数
S[rooty] = rootx; //小树y合并到大树x
}
else {
S[rooty] += S[rootx]; //累加结点总数
S[rootx] = rooty; //小树x合并到大树y
}
}
//判断元素x和y是否属于同一集合
int IsSame(int S[],int x,int y){
if (Find(S,x)==Find(S,y))
return 1;
else
return 0;
}
int main(){
int S[SIZE];
Initial(S);
printf("初始状态:");
for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
printf("\n");
Union(S,0,1);
printf("1 合并后的状态:");
for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
printf("\n");
Union(S,1,2);
Union(S,2,3);
Union(S,3,4);
Union(S,4,5);
printf("2-5合并后的状态:");
for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
printf("\n");
Union(S,0,7);
printf("7 合并后的状态:");
for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
printf("\n\n");
return 0;
}初始状态:-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 1 合并后的状态:-2 0 -1 -1 -1 -1 -1 -1 -1 -1 2-5合并后的状态:-6 0 0 0 0 0 -1 -1 -1 -1 7 合并后的状态:-7 0 0 0 0 0 -1 0 -1 -1
#并查集-按秩合并
#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
const int maxn = 5005;
int Fa[maxn],Rank[maxn];
//初始化(按秩合并)
void init(int n){
for (int i=0; i<n; i++){
Fa[i] = i;
Rank[i] = 1;
}
}
int find(int x){
return x == Fa[x]? x:(Fa[x] = find(Fa[x]));//路径压缩
}
//合并(按秩合并)
void merge(int i, int j) {
int x = find(i), y = find(j);
if (Rank[x] < Rank[y]){ //小树合并到大树
Fa[x] = y;
}
else{
Fa[y] = x;
}
// 合并完更新rank秩
if (Rank[x] == Rank[y] && x!=y){
Rank[y]++;
}
}
int main(){
init(SIZE);
printf("初始状态:");
for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
printf("\n");
merge(0,1);
printf("1 合并后的状态:");
for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
printf("\n");
merge(1,2);
merge(2,3);
merge(3,4);
merge(4,5);
printf("2-5合并后的状态:");
for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
printf("\n");
merge(2,7);
printf("7 合并后的状态:");
for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
printf("\n\n");
return 0;
}初始状态:0(1) 1(1) 2(1) 3(1) 4(1) 5(1) 6(1) 7(1) 8(1) 9(1) 1 合并后的状态:0(1) 0(2) 2(1) 3(1) 4(1) 5(1) 6(1) 7(1) 8(1) 9(1) 2-5合并后的状态:0(1) 0(2) 0(2) 0(2) 0(2) 0(2) 6(1) 7(1) 8(1) 9(1) 7 合并后的状态:0(1) 0(2) 0(2) 0(2) 0(2) 0(2) 6(1) 0(2) 8(1) 9(1)
#图形结构
#1.邻接矩阵-无向图
/* 图
邻接矩阵(Adjacency Matrix)
存储方式是用两个数组来表示图。
一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
无向图
C实现
*/
#include <stdio.h>
#include <string.h>
#define MaxVertexNum 100 //顶点数目最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
#define numVertexes 6 // 顶点个数,用于visited数组
#define numEdges 7 // 边个数
typedef struct
{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
int vexnum, edgenum; //图的顶点数和弧数
}MGraph;
void create_Graph(MGraph *G);
void create_Graph_ByArray(MGraph *G, int edges[][3]);
void print_Matrix(MGraph G);
void DFS(MGraph G,int v);
void DFSTraverse(MGraph G);
void BFS(MGraph G, int v);
void BFSTraverse(MGraph G);
int main()
{
MGraph G;
// 创建无向图方法1
//create_Graph(&G);
// 创建无向图方法2
//这里可以使用int,因为edge的EdgeType是int
int edges[numEdges][3] = { // 边的起点序号,终点序号,权值
{1,2,5},
{1,3,1},
{1,4,6},
{2,5,3},
{2,6,4},
{3,5,3},
{4,6,2}
};
create_Graph_ByArray(&G,edges);
print_Matrix(G);
printf("\nDFS:");
DFS(G,0);
printf("\nBFS:");
BFS(G,0);
printf("\nBFS直接遍历(非连通图):");
BFSTraverse(G);
return 0;
}
// 创建无向图
void create_Graph(MGraph *G){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建无向图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//图的初始化init
for (i=0; i<G->vexnum; i++){
for (j=0; j<G->vexnum; j++){
if (i == j)
G->Edge[i][j] = 0; //结点自身
else
G->Edge[i][j] = 32767; //初始都为表示∞
}
}
//顶点信息存入顶点表
for (i=0; i<G->vexnum; i++){
// printf("请输入第%d个顶点的信息(int):",i+1);
// scanf("%d", &G->Vex[i]);
//这里不输入了,暂时默认0开始
G->Vex[i] = i+1;
}
printf("\n");
//输入无向图边的信息
for (i=0; i<G->edgenum; i++){
printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");
scanf("%d%d%d", &start, &end, &w);
G->Edge[start-1][end-1] = w;
G->Edge[end-1][start-1] = w; //无向图具有对称性
}
}
// 创建无向图
void create_Graph_ByArray(MGraph *G, int edges[][3]){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建无向图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//图的初始化init
for (i=0; i<G->vexnum; i++){
for (j=0; j<G->vexnum; j++){
if (i == j)
G->Edge[i][j] = 0; //结点自身
else
G->Edge[i][j] = 32767; //初始都为表示∞
}
}
//顶点信息存入顶点表
for (i=0; i<G->vexnum; i++){
// printf("请输入第%d个顶点的信息(int):",i+1);
// scanf("%d", &G->Vex[i]);
//这里不输入了,暂时默认0开始
G->Vex[i] = i+1;
}
printf("\n");
// 输入无向图边的信息
for (i=0; i<G->edgenum; i++){
start = edges[i][0];
end = edges[i][1];
w = edges[i][2];
G->Edge[start-1][end-1] = w;
G->Edge[end-1][start-1] = w; //无向图具有对称性
}
}
// 输出图
void print_Matrix(MGraph G){
int i, j;
printf("\n图的顶点为:");
for (i=0; i<G.vexnum; i++)
printf("%d ", G.Vex[i]);
printf("\n输出邻接矩阵:\n");
// 横坐标
printf(" "); //表示行坐标,前面空格留给纵坐标
for (i=0; i<G.vexnum; i++)
printf("%5d", G.Vex[i]);
printf("\n");
for (i=0; i<G.vexnum; i++){
// 纵坐标
printf("\n%d", G.Vex[i]);
// 输出邻接矩阵
for (j=0; j<G.vexnum; j++){
if (G.Edge[i][j] == 32767)
printf("%7s", "∞");
else
printf("%5d", G.Edge[i][j]);
}
printf("\n");
}
}
// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){
// 访问顶点x
printf("%d",G.Vex[x]);
visited[x]=1; //设已访问标记
// 遍历x的邻接顶点
for(int v=0; v<G.vexnum; v++){
//i为x的尚未访问的邻接顶点
if(!visited[v] && G.Edge[x][v] != 32767){
DFS(G, v);
}
}
}
//对非连通图进行深度优先遍历
void DFSTraverse(MGraph G){
//把所有结点全部标记为false,表示没有访问过
for(int v=0; v<G.vexnum; v++){
visited[v] = 0;
}
for(int v=0; v<G.vexnum; v++){ //从v=0开始遍历
if(!visited[v]){
DFS(G, v);
}
}
}
/// @brief /辅助队列
typedef struct{
int data[numVertexes];
int f,r;
}Que;
void InitQueue(Que &Q){
Q.f=Q.r=0;
}
void In(Que &Q,int e){
if ((Q.r+1)%numVertexes==Q.f) return;
Q.data[Q.r]=e;
Q.r=(Q.r+1)%numVertexes;
}
void Out(Que &Q,int &e){
if(Q.f==Q.r) return;
e=Q.data[Q.f];
Q.f=(Q.f+1)%numVertexes;
}
// ------------------------- BFS 广度优先遍历------------------------
// 对连通图进行广度优先遍历
void BFS(MGraph G, int v){
Que Q;
InitQueue(Q); //初始化一辅助用的队列
//把所有结点全部标记为false,表示没有访问过
for(int i=0; i<G.vexnum; i++){
visited[i] = 0;
}
printf("%d",G.Vex[v]);
visited[v]=1;
In(Q,v);
while(Q.f!=Q.r){
Out(Q,v);
//把出队结点的相邻的所有结点入队
for(int w=0; w<G.vexnum; w++){
if(!visited[w] && G.Edge[v][w] != 32767){
printf("%d",G.Vex[w]);
visited[w]=1;
In(Q,w);
}
}
}
}
// 对非连通图的广度遍历
void BFSTraverse(MGraph G){
Que Q;
InitQueue(Q); //初始化一辅助用的队列
int v;
//把所有结点全部标记为false,表示没有访问过
for(v=0; v<G.vexnum; v++){
visited[v] = 0;
}
for(v=0; v<G.vexnum; v++){ //这里是从0开始
//若是未访问过就处理
if(!visited[v]){
printf("%d",G.Vex[v]);
visited[v]=1;
In(Q,v);
while(Q.f!=Q.r){
Out(Q,v);
//把出队结点的相邻的所有结点入队
for(int w=0; w<G.vexnum; w++){
if(!visited[w] && G.Edge[v][w] != 32767){
printf("%d",G.Vex[w]);
visited[w]=1;
In(Q,w);
}
}
}
}
}
}请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):1 2 5 请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):1 3 1 请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):1 4 6 请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):2 5 3 请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):2 6 4 请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):3 5 3 请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):4 6 2
图的顶点为:1 2 3 4 5 6 输出邻接矩阵: 1 2 3 4 5 6
1 0 5 1 6 ∞ ∞
2 5 0 ∞ ∞ 3 4
3 1 ∞ 0 ∞ 3 ∞
4 6 ∞ ∞ 0 ∞ 2
5 ∞ 3 3 ∞ 0 ∞
6 ∞ 4 ∞ 2 ∞ 0
DFS:125364 BFS:123456 BFS直接遍历(非连通图):123456
#2.邻接矩阵-有向图
/* 图
邻接矩阵(Adjacency Matrix)
存储方式是用两个数组来表示图。
一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
有向图
C实现
*/
#include <stdio.h>
#include <string.h>
#define MaxVertexNum 100 //顶点数目最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
#define numVertexes 6 // 顶点个数,用于visited数组
#define numEdges 7 // 边个数
typedef struct
{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
int vexnum, edgenum; //图的顶点数和弧数
}MGraph;
void create_Graph(MGraph *G);
void create_Graph_ByArray(MGraph *G, int edges[][3]);
void print_Matrix(MGraph G);
void DFS(MGraph G,int v);
void DFSTraverse(MGraph G);
void BFS(MGraph G, int v);
void BFSTraverse(MGraph G);
int main()
{
MGraph G;
// 创建有向图方法1
//create_Graph(&G);
// 创建有向图方法2
//这里可以使用int,因为edge的EdgeType是int
int edges[numEdges][3] = { // 边的起点序号,终点序号,权值
{1,2,5},
{3,1,1},
{4,1,6},
{5,2,3},
{5,3,3},
{6,2,4},
{6,4,2}
};
create_Graph_ByArray(&G,edges);
print_Matrix(G);
printf("\nDFS:");
DFS(G,0);
printf("\nDFS直接遍历(非连通图):");
DFSTraverse(G);
printf("\nBFS:");
BFS(G,0);
printf("\nBFS直接遍历(非连通图):");
BFSTraverse(G);
return 0;
}
// 创建有向图
void create_Graph(MGraph *G){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建有向图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//图的初始化init
for (i=0; i<G->vexnum; i++){
for (j=0; j<G->vexnum; j++){
if (i == j)
G->Edge[i][j] = 0; //结点自身
else
G->Edge[i][j] = 32767; //初始都为表示∞
}
}
//顶点信息存入顶点表
for (i=0; i<G->vexnum; i++){
// printf("请输入第%d个顶点的信息(int):",i+1);
// scanf("%d", &G->Vex[i]);
//这里不输入了,暂时默认0开始
G->Vex[i] = i+1;
}
printf("\n");
//输入有向图边的信息
for (i=0; i<G->edgenum; i++){
printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");
scanf("%d%d%d", &start, &end, &w);
G->Edge[start-1][end-1] = w;
//有向图不具有对称性
}
}
// 创建有向图
void create_Graph_ByArray(MGraph *G, int edges[][3]){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建有向图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//图的初始化init
for (i=0; i<G->vexnum; i++){
for (j=0; j<G->vexnum; j++){
if (i == j)
G->Edge[i][j] = 0; //结点自身
else
G->Edge[i][j] = 32767; //初始都为表示∞
}
}
//顶点信息存入顶点表
for (i=0; i<G->vexnum; i++){
// printf("请输入第%d个顶点的信息(int):",i+1);
// scanf("%d", &G->Vex[i]);
//这里不输入了,暂时默认0开始
G->Vex[i] = i+1;
}
printf("\n");
// 输入有向图边的信息
for (i=0; i<G->edgenum; i++){
start = edges[i][0];
end = edges[i][1];
w = edges[i][2];
G->Edge[start-1][end-1] = w;
//有向图不具有对称性
}
}
// 输出图
void print_Matrix(MGraph G){
int i, j;
printf("\n图的顶点为:");
for (i=0; i<G.vexnum; i++)
printf("%d ", G.Vex[i]);
printf("\n输出邻接矩阵:\n");
// 横坐标
printf(" "); //表示行坐标,前面空格留给纵坐标
for (i=0; i<G.vexnum; i++)
printf("%5d", G.Vex[i]);
printf("\n");
for (i=0; i<G.vexnum; i++){
// 纵坐标
printf("\n%d", G.Vex[i]);
// 输出邻接矩阵
for (j=0; j<G.vexnum; j++){
if (G.Edge[i][j] == 32767)
printf("%7s", "∞");
else
printf("%5d", G.Edge[i][j]);
}
printf("\n");
}
}
// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){
// 访问顶点x
printf("%d",G.Vex[x]);
visited[x]=1; //设已访问标记
// 遍历x的邻接顶点
for(int v=0; v<G.vexnum; v++){
//i为x的尚未访问的邻接顶点
if(!visited[v] && G.Edge[x][v] != 32767){
DFS(G, v);
}
}
}
//对非连通图进行深度优先遍历
void DFSTraverse(MGraph G){
//把所有结点全部标记为false,表示没有访问过
for(int v=0; v<G.vexnum; v++){
visited[v] = 0;
}
for(int v=0; v<G.vexnum; v++){ //从v=0开始遍历
if(!visited[v]){
DFS(G, v);
}
}
}
/// @brief /辅助队列
typedef struct{
int data[numVertexes];
int f,r;
}Que;
void InitQueue(Que &Q){
Q.f=Q.r=0;
}
void In(Que &Q,int e){
if ((Q.r+1)%numVertexes==Q.f) return;
Q.data[Q.r]=e;
Q.r=(Q.r+1)%numVertexes;
}
void Out(Que &Q,int &e){
if(Q.f==Q.r) return;
e=Q.data[Q.f];
Q.f=(Q.f+1)%numVertexes;
}
// ------------------------- BFS 广度优先遍历------------------------
// 对连通图进行广度优先遍历
void BFS(MGraph G, int v){
Que Q;
InitQueue(Q); //初始化一辅助用的队列
//把所有结点全部标记为false,表示没有访问过
for(int i=0; i<G.vexnum; i++){
visited[i] = 0;
}
printf("%d",G.Vex[v]);
visited[v]=1;
In(Q,v);
while(Q.f!=Q.r){
Out(Q,v);
//把出队结点的相邻的所有结点入队
for(int w=0; w<G.vexnum; w++){
if(!visited[w] && G.Edge[v][w] != 32767){
printf("%d",G.Vex[w]);
visited[w]=1;
In(Q,w);
}
}
}
}
// 对非连通图的广度遍历
void BFSTraverse(MGraph G){
Que Q;
InitQueue(Q); //初始化一辅助用的队列
int v;
//把所有结点全部标记为false,表示没有访问过
for(v=0; v<G.vexnum; v++){
visited[v] = 0;
}
for(v=0; v<G.vexnum; v++){ //这里是从0开始
//若是未访问过就处理
if(!visited[v]){
printf("%d",G.Vex[v]);
visited[v]=1;
In(Q,v);
while(Q.f!=Q.r){
Out(Q,v);
//把出队结点的相邻的所有结点入队
for(int w=0; w<G.vexnum; w++){
if(!visited[w] && G.Edge[v][w] != 32767){
printf("%d",G.Vex[w]);
visited[w]=1;
In(Q,w);
}
}
}
}
}
}#3.邻接表
/* 图
邻接表(Adjacency List)
存储方式是结合了 顺序存储 + 链式存储方法,减少了邻接矩阵不必要的浪费。
无向图 与 有向图 的区别:就是一条edge是否添加两次到两端节点
这里默认是无向图,但是在代码中注释了有向图
C实现
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MaxVertexNum 100 //顶点数目最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
#define numVertexes 5 // 顶点个数,用于visited数组
#define numEdges 7 // 边个数
//边表结点
typedef struct EdgeNode{
int adjvex; //该弧所指向的顶点的下标或者位置
EdgeType weight; //权值,对于非网图可以不需要
struct EdgeNode *next; //指向下一个邻接点
}EdgeNode;
//顶点表结点
typedef struct VertexNode{
VertexType data; //顶点域,存储顶点信息
EdgeNode *firstedge; //边表头指针
}VertexNode, AdjList[MaxVertexNum];
// AdjList[MaxVertexNum]是静态的链表
//邻接表
typedef struct{
AdjList adjList;
int vexnum, edgenum; //图的当前顶点数和边数/弧数
}LinkGraph;
void create_Graph(LinkGraph *G);
void create_Graph_ByArray(LinkGraph *G, int edges[][3]);
void print_Graph(LinkGraph G);
int main()
{
LinkGraph G;
// 创建图方法1
// create_Graph(&G);
// 创建图方法2
// 这里可以使用int,因为edge的EdgeType是int
int edges[numEdges][3] = { // 边的起点序号,终点序号,权值
{1,2,1},
{1,5,1},
{2,3,1},
{2,4,1},
{2,5,1},
{3,4,1},
{4,5,1}
};
create_Graph_ByArray(&G,edges);
print_Graph(G);
return 0;
}
// 创建图
void create_Graph(LinkGraph *G){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//初始化顶点
for (i=0; i<G->vexnum; i++){
G->adjList[i].data = i+1; //顶点初始化
G->adjList[i].firstedge = NULL; //边表头指针初始化为空
}
//初始化边
for (i=0; i<G->edgenum; i++){
printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");
scanf("%d%d%d", &start, &end, &w);
// 创建边表结点
// 有向图,只需要创建一个结点
EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = end-1; //终点序号
e->weight = w;
//将结点e插入顶点表start的边表中
e->next = G->adjList[start-1].firstedge;
G->adjList[start-1].firstedge = e;
// 无向图,还要插入终点序号的边表
//创建边表结点
EdgeNode *e_ = (EdgeNode*)malloc(sizeof(EdgeNode));
e_->adjvex = start-1; //起点序号
e_->weight = w;
//将结点e_插入顶点表end的边表中
e_->next = G->adjList[end-1].firstedge;
G->adjList[end-1].firstedge = e_;
}
}
// 使用数组创建图
void create_Graph_ByArray(LinkGraph *G, int edges[][3]){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//初始化顶点
for (i=0; i<G->vexnum; i++){
G->adjList[i].data = i+1; //顶点初始化
G->adjList[i].firstedge = NULL; //边表头指针初始化为空
}
//初始化边
for (i=0; i<G->edgenum; i++){
start = edges[i][0];
end = edges[i][1];
w = edges[i][2];
// 创建边表结点
// 有向图,只需要创建一个结点
EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = end-1; //终点序号
e->weight = w;
//将结点e插入顶点表start的边表中
e->next = G->adjList[start-1].firstedge;
G->adjList[start-1].firstedge = e;
// 无向图,还要插入终点序号的边表
//创建边表结点
EdgeNode *e_ = (EdgeNode*)malloc(sizeof(EdgeNode));
e_->adjvex = start-1; //起点序号
e_->weight = w;
//将结点e_插入顶点表end的边表中
e_->next = G->adjList[end-1].firstedge;
G->adjList[end-1].firstedge = e_;
}
}
// 输出图
void print_Graph(LinkGraph G){
int i, j;
printf("\n图的顶点为:");
for (i=0; i<G.vexnum; i++){
printf("%d ", G.adjList[i].data);
}
// 输出邻接表
printf("\n图的邻接矩阵为:\n");
for (i=0; i<G.vexnum; i++){ //遍历顶点
printf("%d. %d:> ",i, G.adjList[i].data); //输出顶点
EdgeNode *p = G.adjList[i].firstedge; //边结构
while (p){ //遍历边
printf("%d-->", p->adjvex+1);
p = p->next;
}
printf("Null.\n");
}
}#4.最小生成树
/* 图
邻接矩阵(Adjacency Matrix)
存储方式是用两个数组来表示图。
一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
无向图
C实现
*/
#include <stdio.h>
#include <string.h>
#define MaxVertexNum 100 //顶点数目最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
#define numVertexes 6 // 定义顶点个数,用于visited数组
#define numEdges 10 // 定义边个数
#define INFINITY 32767 // 定义∞,infinity
typedef struct
{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
int vexnum, edgenum; //图的顶点数和弧数
}MGraph;
void create_Graph(MGraph *G);
void create_Graph_ByArray(MGraph *G, int edges[][3]);
void print_Matrix(MGraph G);
void DFS(MGraph G,int v);
void DFSTraverse(MGraph G);
void BFS(MGraph G, int v);
void BFSTraverse(MGraph G);
void MiniSpanTree_Prim(MGraph G); //最小生成树,prim
void MiniSpanTree_Kruskal(MGraph G); //最小生成树,kruskal
int Find(int *parent, int f);
void Swap(int edges[][3], int i, int j);
int main()
{
MGraph G;
// 创建无向图方法1
//create_Graph(&G);
// 创建无向图方法2
//这里可以使用int,因为edge的EdgeType是int
int edges[numEdges][3] = { // 边的起点序号(从1开始),终点序号,权值
{1,2,6},
{1,3,5},
{1,4,1},
{2,4,5},
{2,5,3},
{3,4,4},
{3,6,2},
{4,5,6},
{4,6,4},
{5,6,6}
};
create_Graph_ByArray(&G,edges);
print_Matrix(G);
printf("\nDFS:");
DFS(G,0);
printf("\nBFS:");
BFS(G,0);
printf("\nBFS直接遍历(非连通图):");
BFSTraverse(G);
printf("\n最小生成树Prim:\n");
MiniSpanTree_Prim(G);
printf("\n最小生成树Kruskal:\n");
MiniSpanTree_Kruskal(G);
return 0;
}
// 创建无向图
void create_Graph(MGraph *G){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建无向图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//图的初始化init
for (i=0; i<G->vexnum; i++){
for (j=0; j<G->vexnum; j++){
if (i == j)
G->Edge[i][j] = 0; //结点自身
else
G->Edge[i][j] = INFINITY; //初始都为表示∞
}
}
//顶点信息存入顶点表
for (i=0; i<G->vexnum; i++){
// printf("请输入第%d个顶点的信息(int):",i+1);
// scanf("%d", &G->Vex[i]);
//这里不输入了,暂时默认0开始
G->Vex[i] = i+1;
}
printf("\n");
//输入无向图边的信息
for (i=0; i<G->edgenum; i++){
printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");
scanf("%d%d%d", &start, &end, &w);
G->Edge[start-1][end-1] = w;
G->Edge[end-1][start-1] = w; //无向图具有对称性
}
}
// 创建无向图
void create_Graph_ByArray(MGraph *G, int edges[][3]){
int i, j;
int start, end; //边的起点序号、终点序号
int w; //边上的权值
// 所创建无向图的顶点数和边数(用空格隔开)。这里也可以输入
G->vexnum = numVertexes;
G->edgenum = numEdges;
printf("\n");
//图的初始化init
for (i=0; i<G->vexnum; i++){
for (j=0; j<G->vexnum; j++){
if (i == j)
G->Edge[i][j] = 0; //结点自身
else
G->Edge[i][j] = INFINITY; //初始都为表示∞
}
}
//顶点信息存入顶点表
for (i=0; i<G->vexnum; i++){
// printf("请输入第%d个顶点的信息(int):",i+1);
// scanf("%d", &G->Vex[i]);
//这里不输入了,暂时默认0开始
G->Vex[i] = i+1;
}
printf("\n");
// 输入无向图边的信息
for (i=0; i<G->edgenum; i++){
start = edges[i][0];
end = edges[i][1];
w = edges[i][2];
G->Edge[start-1][end-1] = w;
G->Edge[end-1][start-1] = w; //无向图具有对称性
}
}
// 输出图
void print_Matrix(MGraph G){
int i, j;
printf("\n图的顶点为:");
for (i=0; i<G.vexnum; i++)
printf("%d ", G.Vex[i]);
printf("\n输出邻接矩阵:\n");
// 横坐标
printf(" "); //表示行坐标,前面空格留给纵坐标
for (i=0; i<G.vexnum; i++)
printf("%5d", G.Vex[i]);
printf("\n");
for (i=0; i<G.vexnum; i++){
// 纵坐标
printf("\n%d", G.Vex[i]);
// 输出邻接矩阵
for (j=0; j<G.vexnum; j++){
if (G.Edge[i][j] == INFINITY)
printf("%7s", "∞");
else
printf("%5d", G.Edge[i][j]);
}
printf("\n");
}
}
// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){
// 访问顶点x
printf("%d",G.Vex[x]);
visited[x]=1; //设已访问标记
// 遍历x的邻接顶点
for(int v=0; v<G.vexnum; v++){
//i为x的尚未访问的邻接顶点
if(!visited[v] && G.Edge[x][v] != INFINITY){
DFS(G, v);
}
}
}
//对非连通图进行深度优先遍历
void DFSTraverse(MGraph G){
//把所有结点全部标记为false,表示没有访问过
for(int v=0; v<G.vexnum; v++){
visited[v] = 0;
}
for(int v=0; v<G.vexnum; v++){ //从v=0开始遍历
if(!visited[v]){
DFS(G, v);
}
}
}
/// @brief /辅助队列
typedef struct{
int data[numVertexes];
int f,r;
}Que;
void InitQueue(Que &Q){
Q.f=Q.r=0;
}
void In(Que &Q,int e){
if ((Q.r+1)%numVertexes==Q.f) return;
Q.data[Q.r]=e;
Q.r=(Q.r+1)%numVertexes;
}
void Out(Que &Q,int &e){
if(Q.f==Q.r) return;
e=Q.data[Q.f];
Q.f=(Q.f+1)%numVertexes;
}
// ------------------------- BFS 广度优先遍历------------------------
// 对连通图进行广度优先遍历
void BFS(MGraph G, int v){
Que Q;
InitQueue(Q); //初始化一辅助用的队列
//把所有结点全部标记为false,表示没有访问过
for(int i=0; i<G.vexnum; i++){
visited[i] = 0;
}
printf("%d",G.Vex[v]);
visited[v]=1;
In(Q,v);
while(Q.f!=Q.r){
Out(Q,v);
//把出队结点的相邻的所有结点入队
for(int w=0; w<G.vexnum; w++){
if(!visited[w] && G.Edge[v][w] != INFINITY){
printf("%d",G.Vex[w]);
visited[w]=1;
In(Q,w);
}
}
}
}
// 对非连通图的广度遍历
void BFSTraverse(MGraph G){
Que Q;
InitQueue(Q); //初始化一辅助用的队列
int v;
//把所有结点全部标记为false,表示没有访问过
for(v=0; v<G.vexnum; v++){
visited[v] = 0;
}
for(v=0; v<G.vexnum; v++){ //这里是从0开始
//若是未访问过就处理
if(!visited[v]){
printf("%d",G.Vex[v]);
visited[v]=1;
In(Q,v);
while(Q.f!=Q.r){
Out(Q,v);
//把出队结点的相邻的所有结点入队
for(int w=0; w<G.vexnum; w++){
if(!visited[w] && G.Edge[v][w] != INFINITY){
printf("%d",G.Vex[w]);
visited[w]=1;
In(Q,w);
}
}//for
}//while
}//if
}//for
}
// -------------- 最小生成树(Minimum Spanning Tree,MST)--------------------
// 最小生成树MST - Prim算法
// 贪心, O(n^2), 适用于稠密图
void MiniSpanTree_Prim(MGraph G){
int i, j;
int v, min; //min是最小权值,v是最小权值的下标
int adjvex[G.vexnum]; //保存相关顶点下标
int lowCost[G.vexnum]; //保存标记各节点加入树的最低代价
//初始化
lowCost[0] = 0; //初始化第一个权值为0,即v0加入生成树
//lowCost的值为0,在这里就是此下标的顶点已经加入生成树
adjvex[0] = 0; //初始化第一个顶点下标为0
for(i=0; i<G.vexnum; i++){
lowCost[i] = G.Edge[0][i]; //将v0顶点与之组成边的权值存入数组
adjvex[i] = 0; //初始化都为v0的下标
}
//寻找
for(i=1; i<G.vexnum; i++){
min = INFINITY; //初始化最小权值为∞,通常设置一个不可能的很大的数字
j = 1; //0已经初始化,从1开始
v = 0;
//循环全部顶点找最小权值
while(j < G.vexnum){
//如果权值不为0且权值小于min
if(lowCost[j]!=0 && lowCost[j]<min){
min = lowCost[j]; //则让当前权值成为最小值
v = j; //将当前最小值的下标存入k
}
j++;
}
printf("(%d, %d)", adjvex[v], v); //打印当前顶点边中权值的最小边
for(j=1; j<G.vexnum; j++){ //修改lowCost数组
//若下标为v顶点各边权值小于此前这些顶点未被加入生成树权值
if(lowCost[j]!=0 && G.Edge[v][j] < lowCost[j]){
lowCost[j] = G.Edge[v][j]; //将较小权值存入lowCost
adjvex[j] = v; //将下标为v的顶点存入adjvex
}
}
}//for
}
// 最小生成树MST - Kruskal算法
// 贪心, O(nlogn), 适用于稀疏图
void MiniSpanTree_Kruskal(MGraph G){
int i, j;
int n, m; //用于判断环路
int edges[G.edgenum][3]; //定义边集数组(边的起点序号(从1开始),终点序号,权值)
int parent[G.vexnum]; //定义一数组用来判断边与边是否形成环路
// 将邻接矩阵G转化为边集数组edges并按照权由小到大排序
//1.先放进去
int k=0; //边集数组的下标
for(i=0; i<G.vexnum-1; i++){
for(j=i+1; j<G.vexnum; j++){
if(G.Edge[i][j] != INFINITY){
edges[k][0] = i;
edges[k][1] = j;
edges[k][2] = G.Edge[i][j];
k++;
}
}
}
//2.对边集数组按照权值从小到大排序
for (i=0; i<G.edgenum; i++){
for (j=i+1; j<G.edgenum; j++){//选择排序
if (edges[i][2] > edges[j][2]){
Swap(edges, i, j);
}
}
}
printf(" 权排序之后的为:\n");
for (i=0; i<G.edgenum; i++){
printf(" (%d, %d) %d\n", edges[i][0], edges[i][1], edges[i][2]);
}
printf(" ----------------------\n最小生成树:");
//初始化parent数组
for(i=0; i<G.vexnum; i++){
parent[i] = 0; //初始化数组为0
}
for(i=0; i<G.edgenum; i++){
n = Find(parent, edges[i][0]);
m = Find(parent, edges[i][1]);
//假如n与m不等,说明此边没有与现有生成树形成环路
if(n != m){
//将此边的结尾顶点放入下标为起点的parent中表示此顶点已经在生成树集合中
parent[n] = m;
printf("(%d, %d: %d)", edges[i][0], edges[i][1], edges[i][2]);
}
}
}
//交换头尾、权值
void Swap(int edges[][3], int i, int j){
int t;
t = edges[i][0];
edges[i][0] = edges[j][0];
edges[j][0] = t;
t = edges[i][2];
edges[i][2] = edges[j][2];
edges[j][2] = t;
t = edges[i][1];
edges[i][1] = edges[j][1];
edges[j][1] = t;
}
//并查集查找连线顶点(树根节点)的尾部下标
int Find(int *parent, int f){
while(parent[f] > 0){
f = parent[f];
}
return f;
}图的顶点为:1 2 3 4 5 6 输出邻接矩阵: 1 2 3 4 5 6
1 0 6 5 1 ∞ ∞
2 6 0 ∞ 5 3 ∞
3 5 ∞ 0 4 ∞ 2
4 1 5 4 0 6 4
5 ∞ 3 ∞ 6 0 6
6 ∞ ∞ 2 4 6 0
DFS:124365 BFS:123456 BFS直接遍历(非连通图):123456 最小生成树Prim: (0, 3)(3, 2)(2, 5)(3, 1)(1, 4) 最小生成树Kruskal: 权排序之后的为: (0, 3) 1 (2, 5) 2 (1, 4) 3 (2, 3) 4 (3, 5) 4 (0, 2) 5 (1, 3) 5 (3, 4) 6 (0, 1) 6 (4, 5) 6
最小生成树:(0, 3: 1)(2, 5: 2)(1, 4: 3)(2, 3: 4)(1, 3: 5)