数据结构C

数据结构

线性表

线性表是 具有相同数据类型的 n个数据元素 的有限序列(有次序),其中n为表长,当n=0时 线性表是一个空表。

若用L命名线性表,则其一般表示为

L = {a1,a2,…,ai,ai+1,…,an}

几个概念:

ai是线性表中的“第i个”元素线性表中的位序。(位序从1开始,数组下标从0开始)

a1是 表头元素,an是表尾元素

除了第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

什么时候要传入参数的引用“&”–对参数的修改结果需要“带回来”

“&”指向的是对象本身,不占用对象的存储空间,而指针本身是一个变量,是需要分配存储空间的,里面存储对象的地址,通过对象地址就能访问,操作对象。所以引用和指针都可以访问对象,作用是类似的。

顺序表

顺序表–用顺序表存储的方式实现线性表顺序存储。

存储结构:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

typedef struct{int num;//号数int people;//人数
} Customer;

如何知道一个数据元素大小?

sizeof(ElemType)

顺序表的静态分配

流程:

①在内存中分配 存储顺序表L各个数据元素 的连续存储空间。包括:MaxSize*sizeof(ElemType)和存储length的空间

②把各个数据元素的值设为默认值

③将Length的值设为0

#include<stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct{ElemType data[MaxSize];//用静态的“数组”存放数据元素int length;//顺序表的当前长度
}SqList;       //顺序表的类型定义(静态分配方式)//基本操作--初始化一个顺序表
void InitList(SqList &L){for(int i=0;i<MaxSize;i++)L.data[i]=0;//将所有数据元素设置为默认初始值L.length=0;//顺序表初始长度为0
}int main(){SqList L; //声明一个顺序表InitList(L);//初始化顺序表//....未完待续,后续操作return 0;
}

//不初始化数据元素,内存不刷0

#include<stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct{ElemType data[MaxSize];//用静态的“数组”存放数据元素int length;//顺序表的当前长度
}SqList;       //顺序表的类型定义(静态分配方式)//基本操作--初始化一个顺序表
//没有设置数据元素的默认值,内存中会有遗留的“脏数据”
void InitList(SqList &L){L.length=0;//顺序表初始长度为0
}int main(){SqList L; //声明一个顺序表InitList(L);//初始化顺序表//尝试“违规”打印出整个data数组for(int i =0;i<L.length;i++)printf("data[%d]=%d\n",i,L.data[i]);return 0;
}

顺序表的静态分配过程中,如果“数组”存满了怎么办?

可以放弃治疗,顺序表的表长刚开始确定后就无法更改(存储空间是静态的)

顺序表的动态分配

key:动态申请和释放内存空间

c —— malloc、free函数

#include<stdlib.h>//malloc、free函数的头文件
L.data = (ElemType *) malloc(sizeof(ElemType)* InitSize);   
//第一个(ElemType *),强制转换成所定义的数据类型所对应的指针
//malloc用于申请一整片连续的内存空间,这整片连续的内存空间有一个起始的内存地址。
//malloc执行结束之后,会返回一个指向这整片存储空间开始地址的指针

c++ —— new、delete关键字

#include<stdio.h>
#include<stdlib.h>//malloc、free函数的头文件
#define MaxSize 10 //定义最大长度
typedef struct{ElemType *data;//指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length;  //顺序表的当前长度
}SqList;         //顺序表的类型定义(动态分配方式)//基本操作--初始化一个顺序表
//没有设置数据元素的默认值,内存中会有遗留的“脏数据”
void InitList(SqList &L){L.length=0;//顺序表初始长度为0
}//增加动态数组的长度
void IncreaseSize(SeqList &L, int len){int *p = L.data;L.data = (int *) malloc(sizeof(int)* InitSize);     for(int i=0; i<L.length; i++){L.data[i]=p[i]; //将数据复制到新区域}L.MaxSize=L.MaxSize+len;//顺序表最大长度增加lenfree(p);//释放原来的内存空间
}int main(){SqList L; //声明一个顺序表InitList(L);//初始化顺序表//....往顺序表中随便插入几个元素....return 0;
}

顺序表的特点

①随机访问,即可以在O(1)时间内找到第i个元素。代码实现:data[i-1];静态分配、动态分配都一样。

②存储密度高,每个节点只存储数据元素。

③拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)

④插入、删除操作不方便,需要移动大量元素。

顺序表的插入

ListInsert(&L, i, e): 插入操作。在表L中的第i个位置上插入指定元素e。

void ListInsert(SqList &L, int i, int e){for(int j=L.length; j>=i; j--)//将第i个元素及之后的元素后移L.data[j]=L.data[j-1];L.data[i-1]=e;//在位置i处放入e,数组是从0开始,注意位序L.length++;   //长度加1
}

好的算法,应该具有“健壮性”。能处理异常情况,并给使用者反馈。

#include<stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct{ElemType data[MaxSize];//用静态的“数组”存放数据元素int length;//顺序表的当前长度
}SqList;       //顺序表的类型定义(静态分配方式)bool ListInsert(SqList &L, int i, int e){if(i<1||i>L.length+1)//判断i的范围是否有效(注意i从1开始算起,其为位序)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,数组是从0开始,注意位序L.length++;   //长度加1return true;
}int main()
{SqList L; //声明一个顺序表InitList(L);//初始化顺序表//...此处省略一些代码,插入几个元素ListInsert(L,3,3);return 0;
}

顺序表的删除

ListDelete(&L, i, &e): 删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

#include<stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct{ElemType data[MaxSize];//用静态的“数组”存放数据元素int length;//顺序表的当前长度
}SqList;       //顺序表的类型定义(静态分配方式)bool ListDelete(SqList &L, int i, int &e){if(i<1||i>L.length) //判断i的范围是否有效return false;e=L.data[i-1]; //将被删除的元素赋值给efor(int j=i;j<L.length;j++)//将第i个位置后的元素前移L.data[j-1]=L.data[j];L.length--;    //线性表长度减1return true;
}int main()
{SqList L; //声明一个顺序表InitList(L);//初始化顺序表//...此处省略一些代码,插入几个元素int e = -1; //用变量“e“把删除的元素"带回来"if(ListInsert(L,3,e))printf("已删除第3个元素,删除元素值为=%d\n",e);elseprintf("位序i不合法,删除失败\n");return 0;
}

顺序表的查找

按位查找

//GetElem(L,i): 按位查找操作。获取表L中第i个位置的元素值。
#include<stdio.h>
#define MaxSize 10 //定义最大长度
//typedef struct{
//    ElemType data[MaxSize];//用静态的“数组”存放数据元素
//    int length;//顺序表的当前长度
//}SqList;      //顺序表的类型定义(静态分配方式)typedef struct{ElemType *data;//指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length;  //顺序表的当前长度
}SqList;         //顺序表的类型定义(动态分配方式)ElemType GetElem(SqList L, int i){return L.data[i-1];
}

按值查找

//LocateElem(L,e):按值查找操作。在表L中,从第一个元素开始依次往后检索,查找第一个元素值等于e的元素,并返回其位序。
#include<stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct{ElemType *data;//指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length;  //顺序表的当前长度
}SqList;         //顺序表的类型定义(动态分配方式)//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L, ElemType e){for(int i=0; i<L.length; i++)if(L.data[i]==e)return i+1; //数组下标为i的元素值等于e,返回其位序i+1return 0;//退出循环,说明查找失败
}

基本数据类型:int、char、double、float等可以直接用运算符”==“比较。

结构类型的比较

typedef struct{int num;int people;
} Customer;
//注意:c语言中,结构体的比较不能直接用”==“,需要依次对比各个分量来判断两个结构体是否相等。
bool isCustomerEqual(Customer a, Customer b){if(a.num == b.num && a.people == b.people)return true;elsereturn false;
}

单链表

顺序表:每个结点中只存放数据元素

​ 优点:可以随机存取,存储密度高。

​ 缺点:要求大片连续空间,改变容量不方便。

单链表:每个结点除了存放数据元素外,还要存放指向下一个结点的指针。

​ 优点:不要求大片连续空间,改变容量方便。

​ 缺点:不可以随机存取,要耗费一定空间存放指针。

struct LNode{            //定义单链表节点类型(结点)ElemType data;       //每个节点存放下一数据元素(数据域)struct LNode *next;  //指针指向下一个结点(指针域)
};struct LNode *p=(struct LNode *)malloc(sizeof(struct LNode));//增加一个新的节点:在内存中申请一个结点所需空间,并用指针p指向这个结点。//typedef关键字--数据类型重命名
typedef<数据类型><别名>
typedef struct LNode LNode;
LNode* p = (LNode *)malloc(sizeof(LNode));

用代码定义一个单链表

typedef struct LNode{  //定义单链表结点类型ElemType data;     //每个结点存放一个数据元素struct LNode *next;  //指针指向下一个结点
}LNode, *LinkList;//结点,单链表(结点指针)struct LNode{       // 定义单链表结点类型ElemType data;  // 每个结点存放一个数据元素struct LNode *next; // 指针指向下一个结点
};LNode *GetElem(Linklist L, int i){int j=1;LNode *p = L->next;if(i==0)return L;if(i<1)return NULL;while(p!=NULL && j<i){p=p->next;j++;}return p;
}typedef struct LNode LNode;
typedef struct LNode *LinkList;//要表示一个单链表时,只需要声明一个头指针L,指向单链表的第一个结点
LNode *L; //声明一个指向单链表第一个结点的指针。强调这是一个结点。
或:
LinkList L;//声明一个指向单链表第一个结点的指针。强调这是一个单链表。

头插法建立单链表的算法如下:

LinkList List_HeadInsert(LinkList &L){//逆向建立单链表LNode *s; int x;L=(LinkList)malloc(sizeof(LNode));//创建头节点L->next=NULL;               //初始为空链表scanf("%d",&x);             //输入结点的值while(x!=9999){             //输入9999表示结束s=(LNode*)malloc(sizeof(LNode));//创建新结点s->data = x;s->next = L->next;L->next = s;  //将新节点插入表中,L为头指针scanf("%d",&x);}return L;
}

不带头节点的和带头节点的单链表

#include<stdio.h>
#include<stdlib.h>//malloc、free函数的头文件typedef struct LNode{ //定义单链表结点类型ElemType data;     //每个结点存放一个数据类型struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;//初始化一个空的单链表
bool InitList(LinkList &L){L = NULL;//空表,暂时还没有任何结点,防止脏数据return true;
}//初始化一个单链表(带头结点)
bool InitList1(LinkList &L){L=(LNode *)malloc(sizeof(LNode));//分配一个头节点if(L = NULL)  //内存不足,分配失败return false;L->next = NULL; //头结点之后暂时还没有结点return true;
}//判断单链表是否为空
bool Empty(LinkList L){if(L==NULL)return true;elsereturn false;//return (L==NULL)
}void test(){LinkList L; //声明一个指向单链表的指针//初始化一个空表InitList(L);InitList1(L);//....后续代码....
}

不带头节点V.S.带头结点:

不带头节点,写代码更麻烦,对第一个数据结点和后续数据点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。

头结点是链表里面第一个结点,他的数据域可以不存放任何信息(有时候也会存放链表的长度等等信息),他的指针区域存放的是链表中第一个数据元素的结点(就是传说中的首元结点)存放的地址。

带头结点,写代码更方便,用过都说好。

单链表的查找

单链表的查找分为按位查找和按值查找。

注意:由于带头结点的方便写代码,我们统一使用”双链表“。

GetElem(L, i):按位查找操作。获取表L中第i个位置的元素的值。

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

按位查找

LNode * GetElem(LinkList L, int i){if(i<0)return NULL;LNode *p;//指针p指向当前扫描到的结点int j=0; //当前p指向的是第几个结点p=L;     //L指向头结点,头结点是第0个结点(不存数据)while(p!=NULL && j<i){p=p->next;j++;}return p;
}//时间复杂度:O(n)

按值查找

//找到数据域==e的结点
LNode *LocateElem(LinkList L,ElemType e){LNode *p = L->next;//从第一个结点开始查找数据域为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;
}//时间复杂度:O(n)

注意:单链表不具备”随机访问“的特性,只能依次扫描。

单链表的插入

按位序插入(带头结点)

ListInsert(&L,i,e): 插入操作。在表L中的第i个位置(从1开始)上插入指定元素e。找到第i-1个结点,将新节点插入其后。

//在第i个位置插入元素e(带头结点)
//在表L中的第i个位置(从1开始)上插入指定元素e。找到第i-1个结点,将新节点插入其后。
//即找到第i-1个结点,放在其后面
//边界情况:①i<1,即在头结点位置插入,显然不满足;②i=1,即在结点后面插入,即i-1,第0个结点插入(头结点),可行;③i为最后一个位置,即找到i-1的位置插入typydef struct LNode{ElemType data;struct LNode *next;
}LNode, *LinkList;//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e){if(i<1)return false;LNode *p; //指针p指向当前扫描到的结点int j=0;//当前p指向的第几个结点,(注意:若要插入到表尾,则p指向最后一个结点,当p==NULL时,报错)p = L;//L指向头结点,头结点是第0个结点(不存数据)while(p!=NULL && j<i-1){//循环找到第i-1个结点p=p->next;j++;}if(p==NULL) //i值不合法return false;LNode *s = (LNode *)malloc(sizeof(LNode));s->data = e;s->next = p->next;p->next = s; //将结点s连到p之后return true; //插入成功
}

按位序插入(不带头结点)

不存在第0个结点,因此i=1时需要特殊处理。

typydef struct LNode{ElemType data;struct LNode *next;
}LNode, *LinkList;//在第i个位置插入元素e(不带头结点)
bool ListInsert(LinkList &L, int i, ElemType e){if(i<1)return false;if(i==1){  //插入第1个结点的操作与其他结点操作不同【不带头结点与带头结点的主要区别】LNode * s =(LNode *)malloc(sizeof(LNode));s->data = e;s->next = L;L=s;  //头指针指向新结点。}LNode *p; //指针p指向当前扫描到的结点int j=0;//当前p指向的第几个结点,(注意:若要插入到表尾,则p指向最后一个结点,当p==NULL时,报错)p = L;//L指向头结点,头结点是第0个结点(不存数据)while(p!=NULL && j<i-1){//循环找到第i-1个结点p=p->next;j++;}if(p==NULL) //i值不合法return false;LNode *s = (LNode *)malloc(sizeof(LNode));s->data = e;s->next = p->next;p->next = s; //将结点s连到p之后return true; //插入成功
}

结论:不带头结点写代码更不方便,推荐用带头结点。

指定结点的后插操作

#include<stdio.h>
#include<stdlib.h>//malloc、free函数的头文件
typedef struct LNode {ElemType data;struct LNode* next;
}LNode, * LinkList;//后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode* p, ElemType e) {if (p == NULL)return false;LNode* s = (LNode*)malloc(sizeof(LNode));if (s == NULL) //内存分配失败||某些情况下有可能分配失败(如内存不足)return false;s->data = e;  //用结点s保存数据元素es->next = p->next;p->next = s;  //将结点s连到p之后return true;
}

指定结点的前插操作

#include<stdio.h>
#include<stdlib.h>//malloc、free函数的头文件
typedef struct LNode {ElemType data;struct LNode* next;
}LNode, * LinkList;//前插操作:在p结点之前插入元素e。前插操作方法,先用后插操作,然后两个结点数据调换即可实现
bool InsertPriorNode(LNode* p, ElemType e) {if (p == NULL)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中元素覆盖为ereturn true;
}

按位序删除(带头结点)

ListDelete(&L, i, &e): 删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

头结点可以看成是”第0个“结点。

找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。

bool ListDelete(LinkList &L, int i, ElemType &e){if(i<1)return false;LNode *p;//指针p指向当前扫描到的结点int j=0; //当前p指向的是第几个结点p=L;     //L指向头结点,头结点是第0个结点(不存数据)while(p!=NULL && j<i-1){// 循环找到第i-1个结点p = p -> next;j++;}if(p==NULL) //i值不合法return false;if(p->next == NULL) //第i-1个结点之后已无其他结点return false;LNode *q = p->next;//令q指向被删除结点e = q->data; //用e返回元素的值p->next=q->next; //将*q结点从链中“断开”free(q);return true;  //删除成功}

单链表

#define _crt_secure_no_warnings
#include <stdio.h>
#include <stdlib.h>/******************单链表**************************/typedef struct LNode {  //定义单链表结点类型int data;     //每个结点存放一个数据元素struct LNode* next;  //指针指向下一个结点
}LNode, * LinkList;//结点,单链表(结点指针)//初始化一个空的单链表(不带头结点)
bool InitList(LinkList& L) {L = NULL;//空表,暂时还没有任何结点,防止脏数据return true;
}//初始化一个单链表(带头结点)
bool InitList1(LinkList& L) {L = (LNode*)malloc(sizeof(LNode));//分配一个头节点if (L = NULL)  //内存不足,分配失败return false;L->next = NULL; //头结点之后暂时还没有结点return true;
}//判断单链表是否为空
bool Empty(LinkList L) {if (L == NULL)return true;elsereturn false;//return (L==NULL)
}//求表长度
int length(LinkList L) {int len = 0; //统计表长LNode* p = L;while (p->next != NULL) {p = p->next;len++;}return len;
}//按位查找
LNode* GetElem(LinkList L, int i) {if (i < 0)return NULL;LNode* p;//指针p指向当前扫描到的结点int j = 0; //当前p指向的是第几个结点p = L;     //L指向头结点,头结点是第0个结点(不存数据)while (p != NULL && j < i) {p = p->next;j++;}return p;
}//按值查找,找到数据域==e的结点
LNode* LocateElem(LinkList L, int e) {LNode* p = L->next;//从第一个结点开始查找数据域为e的结点while (p != NULL && p->data != e)p = p->next;return p; //找到后返回该结点的指针,否则返回NULL
}//在第i个位置插入元素e(带头结点)
//在表L中的第i个位置(从1开始)上插入指定元素e。找到第i-1个结点,将新节点插入其后。
//即找到第i-1个结点,放在其后面
//边界情况:①i<1,即在头结点位置插入,显然不满足;②i=1,即在结点后面插入,即i-1,第0个结点插入(头结点),可行;③i为最后一个位置,即找到i-1的位置插入
bool ListInsertH(LinkList& L, int i, int e) {if (i < 1)return false;LNode* p;int j = 0;p = L;while (p != NULL && j < i - 1) {p = p->next;j++;}if (p == NULL)//i值不合法return false;LNode *s = (LNode*)malloc(sizeof(LNode));s->data = e;s->next = p->next;p->next = s;return true;
}//按位序插入,不带头结点
bool ListInsert(LinkList& L, int i, int e) {if (i < 1) {return false;}if (i == 1) {LNode* s = (LNode*)malloc(sizeof(LNode));s->data = e;s->next = L;L = s;}LNode* p;int j = 0;p = L;while (p != NULL && j < i - 1) {p = p->next;j++;}if (p == NULL)return false;LNode* s = (LNode*)malloc(sizeof(LNode));s->data = e;s->next = p->next;p->next = s;return true;
}/*指定结点的前插操作*/
bool InsertPriorNode(LNode* p, int e)
{if (p == NULL) {return false;}LNode* s = (LNode*)malloc(sizeof(LNode));if (s == NULL)return false;s->next = p->next;p ->next = s;s->data = p->data;p->data = e;return true;
}
/*指定结点的后插操作*/
bool InsertNextNode(LNode* p, int e)
{if (p == NULL)return false;LNode* s = (LNode*)malloc(sizeof(LNode));if (s == NULL)return false;s->data = e;s->next = p->next;p->next = s;return true;
}
/* 按位序删除(带头结点)
ListDelete(&L, i, &e): 删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
头结点可以看成是”第0个“结点。
找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。*/
bool ListDelete(LinkList&L, int i, int& e) {if (i < 1)return false;LNode* p;int j = 0;p = L;while (p != NULL && j < i - 1) {p = p->next;j++;}if (p == NULL)return false;LNode* q;q = p->next;e = q->data;p->next = q->next;free(q);return true;
}
bool DeleteNode(LNode* p) {if (p == NULL)return false;LNode* q;q = p->next;p->data = p->next->data;p->next = q->next;free(q);return true;
}

双链表

/**********************双链表*********************************/typedef struct DNode { //定义双链表结点类型int data;          //数据域struct DNode* prior, * next;  //前驱和后继指针
}DNode, * DLinkList;//初始化双链表
bool InitDLinkList(DLinkList& L) {L = (DNode*)malloc(sizeof(DNode)); //分配一个头结点if (L == NULL) //内存不足,分配失败return false;L->prior = NULL;//头结点的prior永远指向NULLL->next = NULL;//头结点之后暂时还没有结点return true;
}//判断双链表是否为空(带头结点)
bool Empty(DLinkList L) {if (L->next == NULL)return true;elsereturn false;
}//在p结点之后插入s结点
bool InsertNextDNode(DNode* p, DNode* s) {if (p == NULL || s == NULL)//非法参数return false;s->next = p->next;//将结点*s插入到结点*p之后if (p->next != NULL)//如果p结点有后继结点p->next->prior = s;s->prior = p;p->next = s;return true;
}//删除p结点的后继节点
bool DeleteNextDNode(DNode* p) {if (p == NULL)return false;DNode* q;q = p->next; //找到p的后继节点qif (q == NULL)return false; //p没有后继节点p->next = q->next;if (q->next != NULL) //q结点不是最后一个结点q->next->prior = p;free(q); //释放结点空间return true;}void DestoryList(DLinkList& L) {//循环释放各个数据点while (L->next != NULL)DeleteNextDNode(L);free(L); //释放头结点L = NULL; //头指针指向NULL
}void testDLinkList() {//初始链表DLinkList L;InitDLinkList(L);//后续代码
}

循环单链表

#define _crt_secure_no_warnings
#include <stdio.h>
#include <stdlib.h>
/*************************1、循环单链表**********************************************/
typedef struct LNode {int  data;struct LNode* next;
}LNode, * LinkList;//初始化一个循环单链表
bool InitList(LinkList& L) {L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点if (L == NULL)  //内存不足,分配失败return false;L->next = L;    //头结点next指向头结点return true;
}//判断循环单链表是否为空
bool Empty(LinkList L) {if (L->next == L)return true;elsereturn false;
}//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode* p) {if (p->next == L)return true;elsereturn false;
}

循环双链表

/*************************2、循环双链表**********************************************/typedef struct DNode { //定义双链表结点类型int data;          //数据域struct DNode* prior, * next;  //前驱和后继指针
}DNode, * DLinkList;//初始化空的循环双链表
bool InitDLinkList(DLinkList& L) {L = (DNode*)malloc(sizeof(DNode)); //分配一个头结点if (L == NULL)   //内存不足,分配失败return false;L->prior = L; //头结点的prior指向头结点L->next = L; //头结点的next指向头结点return true;
}//判断结点p是否为循环单链表的表尾结点
bool isTail(DLinkList L, DNode* p) {if (p->next == L)return true;elsereturn false;
}//在p结点之后插入s结点
bool InsertNextDNode(DNode* p, DNode* s) {if (p == NULL || s == NULL)return false;s->next = p->next;p->next->prior = s;s->prior = p;p->next = s;return true;
}//删除p的后继结点q
bool DeleteNextDNode(DLinkList L, DNode* p) {DNode *q;q = p->next;if (q == L)return false;p->next = q->next;q->next->prior = p;free(q);return true;
}

只允许在一端进行插入或删除操作的线性表;一种操作受限的线性表,只能在栈顶插入、删除;

特点:先进后出;

术语:栈顶、栈底、空栈;

注意:初始化栈,需要分配内存空间;销毁栈,需要设防栈的内存空间。

队列

/*
* 栈Stack:是只允许在一端进行插入或删除操作的线性表
* 队列Queue:是只允许在一端进行插入,在另一端删除的线性表
* 特点:先进先出
*/
#include<stdio.h>
#include<stdlib.h>#define MaxSize 10  //定义队列中元素的最大个数typedef struct {int data[MaxSize]; //用静态数组存放队列元素int front, rear;   //队头指针(指向队头元素)和队尾指针(指向队尾元素的后一个位置,下一个应该插入的位置)
}SqQueue;//初始化队列
void InitQueue(SqQueue& Q) {//初始时  队头、队尾指针指向0Q.rear = 0;Q.front = 0;Q.size = 0;
}//判断队列是否为空:  队尾指针==队头指针时,队列为空
bool QueueEmpty(SqQueue Q) {if (Q.rear == Q.front) //队空条件return true;elsereturn false;
}//入队,只能从队尾入队:
bool EmQueue(SqQueue& Q, int x) {if (Q.size == MaxSize)return false;      //队满则报错Q.data[Q.rear] = x;    //新元素插入队尾Q.rear = (Q.rear + 1) % MaxSize;  //队尾指针加1取模,当队尾指针达到数组的末尾时,将其回到数组的开头,使得队列可以继续从头部插入元素。
}/************************循环队列**********************************/
//用模运算将存储空间在逻辑上变成了“环状”//判断队列是否为空
bool QueueEmpty(SqQueue Q) {if (Q.rear == Q.front)  //队空条件:队尾指针==队头指针return true; elsereturn false;
}//入队
bool EnQueue(SqQueue& Q, int x) {if ((Q.rear + 1) % MaxSize == Q.front)return false;   //队满则报错,队列已满的条件:队尾指针的再下一个位置是队头,其代价是牺牲一个存储单元Q.data[Q.rear] = x;  //新元素插入队尾Q.rear = (Q.rear + 1) % MaxSize; //队尾指针加1取模,用模运算将存储空间在逻辑上变成了“环状”return true;
}//出队,删除一个队头元素,并用x返回
bool DeQue(SqQueue& Q, int& x) {if (Q.rear == Q.front) //判断队空return false; //队空则报错x = Q.data[Q.front];Q.front = (Q.front + 1) % MaxSize; //队头指针后移return true;
}//获得队头元素的值,用x返回
bool GetHead(SqQueue Q, int& x){if (Q.rear == Q.front)return false;  //队空则报错x = Q.data[Q.front];return true;
}//判断队列已满、已空的方法
/*
* 方案一:牺牲一个存储单元
*        队列已满的条件:队尾指针的再下一个位置是队头,即(Q.rear + 1) % MaxSize = Q.front;
*        队空条件:Q.rear = Q.front
* 方案二:增减辅助变量tag、size
*    ①增加辅助变量size
*        队满条件:Q.size = MaxSize
*        队空条件:Q.size = 0
*    ②增加辅助变量tag
*        每次删除操作成功时,都令Q.tag = 0;每次插入操作成功时,都令Q.tag = 1;
*        只有删除操作才导致队空,只有插入操作才可能导致队满;
*        队满条件: front == rear && tag = 1
*        队空条件: front == rear && tag = 0
*        
*/
#define MaxSize 10  //定义队列中元素的最大个数
typedef struct {int data[MaxSize]; //用静态数组存放队列元素int front, rear;   //队头指针(指向队头元素)和队尾指针(指向队尾元素的后一个位置,下一个应该插入的位置)int size; //队列当前长度
}SqQueue;

队列的链式实现

#include<stdio.h>
#include<stdlib.h>typedef struct LinkNode {   //链式队列结点int data;struct LinkNode* next;
}LinkNode;typedef struct {             //链式队列LinkNode* front, * rear; //队列的队头和队尾指针
}LinkQueue; //初始化(带头结点)
void InitQueue(LinkQueue& Q) {//初始时 front。rear都指向头结点Q.front = (LinkNode*)malloc(sizeof(LinkNode));Q.rear = (LinkNode*)malloc(sizeof(LinkNode));Q.front->next = NULL;Q.rear->next = NULL;
}//判断队列是否为空(带头结点)
bool IsEmpty(LinkQueue Q) {if (Q.front == Q.rear)return true;elsereturn false;
}//入队(带头结点)
void EnQueue(LinkQueue& Q, int x) {LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));s->data = x;s->next = NULL;Q.rear->next = s; //新结点插入到rear之后Q.rear = s;  //修改表尾指针
}//出队(带头结点),队头元素出队
bool Dequeue(LinkQueue& Q, int& x) {if (Q.front == Q.rear)return false; //空队LinkNode* p = Q.front->next;x = p->data;  //用变量x返回队头元素Q.front->next = p->next; //修改头结点的next指针if (Q.rear == p) //此次是最后一个结点出队Q.rear = Q.front; //修改rear指针free(p);   //释放结点空间return true;
}//初始化(不带头结点)
void InitQueue(LinkQueue& Q) {//初始时 front。rear都指向头结点Q.front->next = NULL;Q.rear->next = NULL;
}//判断队列是否为空(不带头结点)
bool IsEmpty(LinkQueue Q) {if (Q.front == NULL)return true;elsereturn false;
}//入队(不带头结点)
void EnQueue(LinkQueue& Q, int x) {LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));s->data = x;s->next = NULL;if (Q.front == NULL) { //在空队列中插入第一个元素Q.front = s;       //修改队头队尾指针Q.rear = s;        //不带头结点的队列,第一个元素入队时,需要特别处理}else {Q.rear->next = s; //新结点插入到rear结点之后Q.rear = s;  //修改rear队尾指针}}//出队(不带头结点),队头元素出列
bool Dequeue(LinkQueue& Q, int& x) {if (Q.front == NULL)return false; //空队LinkNode* p = Q.front; //p指向此次出队的结点x = p->data;  //用变量x返回队头元素Q.front = p->next; //修改front指针if (Q.rear == p) { //此次是最后一个结点出队Q.front = NULL; //Q.front指向NULLQ.rear = NULL; //Q.front指向NULL}free(p);   //释放结点空间return true;
}//队满条件
/*
* 顺序存储--预分配的空间耗尽时队满
* 链式存储--一般不会队满,除非内存不足 
*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/3692.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android ViewGroup onDraw为什么没调用

ViewGroup&#xff0c;它本身并没有任何可画的东西&#xff0c;它是一个透明的控件&#xff0c;因些并不会触发onDraw&#xff0c;但是你现在给LinearLayout设置一个背景色&#xff0c;其实这个背景色不管你设置成什么颜色&#xff0c;系统会认为&#xff0c;这个LinearLayout上…

[回馈]ASP.NET Core MVC开发实战之商城系统(开篇)

在编程方面&#xff0c;从来都是实践出真知&#xff0c;书读百遍其义自见&#xff0c;所以实战是最好的提升自己编程能力的方式。 前一段时间&#xff0c;写了一些实战系列文章&#xff0c;如&#xff1a; ASP.NET MVC开发学生信息管理系统VueAntdvAsp.net WebApi开发学生信息…

R语言的水文、水环境模型优化技术及快速率定方法与多模型案例实践

在水利、环境、生态、机械以及航天等领域中&#xff0c;数学模型已经成为一种常用的技术手段。同时&#xff0c;为了提高模型的性能&#xff0c;减小模型误用带来的风险&#xff1b;模型的优化技术也被广泛用于模型的使用过程。模型参数的快速优化技术不但涉及到优化本身而且涉…

Python中的break和continue语句应用举例

Python中的break和continue语句应用举例 在进行Python编程时候&#xff0c;有时需要&#xff0c;对循环中断或跳过某部分语句&#xff0c;此时常会用到break语句或continue语句。本文将通过实际例子阐述这两个语句的用法。 1.break语句 break语句是实现在某个地方中断循环&a…

Java设计模式之行为型-迭代器模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 三、角色设计 四、案例分析 五、总结 一、基础概念 迭代器模式是一种常用的设计模式&#xff0c;它主要用于遍历集合对象&#xff0c;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露该对象的内部表示。 举个简单的…

创建自定义 SpringBoot-Starter之email-spring-boot-starter

参考文章:【SpringBoot】之创建自定义 SpringBoot-Starter_springboot创建starter_王廷云的博客的博客-CSDN博客 源码包和jar: https://download.csdn.net/download/tiantangpw/88045999 自己写的starter;使用的apache-commons-email 包内包含源码和已打包的jar,亲测可用,可以…

5分钟给你破解这套10万赞的生产教程,访谈乔布斯的AI对话数字人视频是怎么做的

本期是赤辰第16期AI项目拆解栏目&#xff1b; 底部准备了7月粉丝福利&#xff0c;看完可以领取&#xff1b; 上周给粉丝们讲解AI动图说话月涨粉20万的案例并给出保姆式教程&#xff0c;粉丝反馈很热烈&#xff0c;都觉得AI强大&#xff0c;有些学员给自己账号做视频&#xff…

大数据与视频技术的融合趋势将带来怎样的场景应用?

视频技术和AI技术的融合是一种新兴的技术趋势&#xff0c;它将改变视频行业的运作方式。视频技术和AI技术的融合主要包括以下几个方面&#xff1a; 1&#xff09;人脸识别技术 人脸识别技术是AI技术的一个重要应用场景。它可以通过对视频中的人脸进行识别和分析&#xff0c;实…

3.9 Bootstrap 分页

文章目录 Bootstrap 分页分页&#xff08;Pagination&#xff09;默认的分页分页的状态分页的大小 翻页&#xff08;Pager&#xff09;默认的翻页对齐的链接翻页的状态 分页 Bootstrap 分页 本章将讲解 Bootstrap 支持的分页特性。分页&#xff08;Pagination&#xff09;&…

Unity平台如何实现RTSP转RTMP推送?

技术背景 Unity平台下&#xff0c;RTSP、RTMP播放和RTMP推送&#xff0c;甚至包括轻量级RTSP服务这块都不再赘述&#xff0c;今天探讨的一位开发者提到的问题&#xff0c;如果在Unity下&#xff0c;实现RTSP播放的同时&#xff0c;随时转RTMP推送出去&#xff1f; RTSP转RTMP…

浙大数据结构第四周之04-树6 Complete Binary Search Tree

题目详情&#xff1a; A Binary Search Tree (BST) is recursively defined as a binary tree which has the following properties: The left subtree of a node contains only nodes with keys less than the nodes key.The right subtree of a node contains only nodes w…

[javascript核心-08] V8 内存管理机制及性能优化

V8 内存管理 V8 本身也是程序&#xff0c;它本身也会申请内存&#xff0c;它申请的内存称为常驻内存&#xff0c;而它又将内存分为堆和栈 栈内存 栈内存介绍 栈用于存放JS 中的基本类型和引用类型指针栈空间是连续的&#xff0c;增加删除只需要移动指针&#xff0c;操作速度…

华为OD真题--分苹果-带答案

有A&#xff0c;B两个同学想要分苹果。A的想法是使用二进制进行&#xff0c;1 1相加不进一位&#xff0c;如&#xff08;9 5 1001 101 12&#xff09;。B同学的想法是使用十进制进行&#xff0c;并且进一位。会输入两组数据&#xff0c;一组是苹果总数&#xff0c;一组分别…

代码随香录day21

235. 二叉搜索树的最近公共祖先 本题思路&#xff1a; 还是要利用二叉搜索树的特性&#xff0c;中序遍历为有序数组。如果pq两个节点都小于root&#xff0c;那么最近公共祖肯定是在他的左子树&#xff0c;如果都大于那么&#xff0c;肯定就在右子树。然后直接return root 代码…

Linux Ubuntu安装RabbitMQ服务

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

【前端知识】React 基础巩固(二十七)——Fragment

React 基础巩固(二十七)——Fragment Fragment Fragment 允许将子列表分组&#xff0c;而无需向 DOM 添加额外节点可以采用语法糖<></>来替代 Fragment&#xff0c;但在需要添加 key 的场景下不能使用此短语 import React, { PureComponent, Fragment } from &q…

Echarts 实现温度计

先上图 <div id="mainOne" style="width: 230px;height:130px;"></div> var dom1 = document.getElementById(mainOne) 核心代码 setTemperature(){var dom1 = document.getElementById(mainOne)var dom2 = document.getElementById(mainTw…

正则表达式与文本处理器

文本处理器三剑客&#xff1a;grep&#xff08;查找&#xff09; sed awk 正则表达式&#xff1a;由一类特殊字符以及文本字符所编写的一种模式&#xff0c;处理文本当中的内容 其中的一些字符不表示字符的字面含义&#xff0c;这些字符表示控制或者通配的功能 通配符&…

在分区工具上,格式化分区和删除分区. 两者有什么不一样吗?

1.格式化分区&#xff1a;就是重建文件系统&#xff0c;等于把目标分区的数据全部清掉。 删除分区&#xff1a;你删除后可以再重新分区&#xff0c;可以分区多个分区&#xff0c;前提是“删除分区”的大小足够大。分了区&#xff0c;还必须格式化&#xff0c;才能用。 只有分了…

ThreadLocal内存泄露原因,如何避免

内存泄露为程序在申请内存后&#xff0c;无法释放已经申请的内存空间&#xff0c;一次内存泄露可以忽略&#xff0c;但是内存泄露堆积后果很严重&#xff0c;无论多少内存迟早被用光。 不再会被使用的对象或者变量占用的内存不能被回收&#xff0c;就是内存泄露。 强引用&…