线性表(Linear List)
基本概念
线性表是由n(n>=0)个类型相同数据元素组成的有限序列。数据元素可由若干个数据对象组成,且一个线性表中的数据元素必须属于同一数据对象。
线性表示n个类型相同数据元素的有限序列,对n>0,除第一个元素无直接前驱,最后一个元素无直接后继外,其余的每个数据元素只有一个直接前驱和直接后继。
线性表的逻辑结构如图:
线性表具有如下特点:
同一性:线性表由同类数据元素组成,每个元素必须属于同一数据类型。
有穷性:线性表由有限个数据元素组成,表长度就是表中数据元素的个数。
线性表中相邻数据元素之间存在着序偶关系。
线性表的顺序存储
线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表的各个元素,使得线性表中在逻辑结构上相邻的数据元素存储在连续的物理存储单元中,即通过数据元素物理存储的连续性来反映数据元素逻辑上的相邻关系。
采用顺序存储结构存储的线性表通常简称为顺序表。可将顺序表归纳为:关系线性化,结点顺序化。
顺序存储表示
定义
用c语言定义线性表的顺序存储结构:
typedef struct{ char data[MAXSIZE]; int len; //数据长度 }List;
那么线性表的存储地址是如何计算的呢?
假设线性表中有n个元素,每个元素占 sizeof(ElemType)个单元,则第一个元素的地址为 LOC(A) ,则第n个元素的地址为: LOC(n) = LOC(A) + (n-1)* sizeof(ElemType) 。
顺序表的存储结构如下:
我们只是定义了顺序表,还没有创建呢!
创建
//初始化顺序表 List* initList(List *L){ int num, i; char ch; //输入顺序表长度 printf("请输入顺序表长度(0<len<100): "); scanf("%d", &num); L->len = num; //输入数据 for(i = 0; i < L->len; i++){ getchar(); printf("请输入第 %d 个数:", i); scanf("%c", &ch); L->data[i] = ch; } return L; }
基本操作
线性表的基本操作有查找,插入,删除。
查找
查找分为两种:1.可以按序号查找 getAsNum(L,i) :查找线性表L中第i个数据元素。2.按内容查找 getAsContent(L, content) :查找顺序表L中的和Content相等的数据元素
【算法思想】:按内容查找运算可采用顺序查找,即从第一个元素开始,依次将表中元素content相比较,若想等,则查找成功,返回该元素在表中的序号;若都不想等,则查找失败,返回-1
【算法描述】按内容查找算法
//按内容查找 int getAsContent(List L, char content){ unsigned int i = 0; while(i >= 0 || i <= L.len - 1){ //顺序扫描表,找到对应元素,或者扫描完则退出 if(L.data[i] != content){ i++; }else{ break; } } if(i <= L.len - 1){ //找到则输出并返回序号 printf("按内容查找成功,第 %d 个位置元素为 %c ", i, L.data[i]); return i; }else{ //未找到 printf("查找失败,没有你所找的元素! "); return ERROR; } }
小结:查找时要注意判断表中没有你要查找的元素的情况,此算法时间复杂度为O(n)。
插入
插入操作指在第i个元素之前插入一个新的元素,这时线性表的长度就变成了n+1了。
【算法思想】:用顺序表作为线性表的存储结构时,由于结点的物理顺序必须和结点的逻辑顺序保持一致,因此必须将原表位置的n,n-1,……,i上的结点依次后移一个位置(此时原表移动对应得位置为n+1,n,……,i+1),空出第i个位置,然后在该位置插入新结点。注意插入的位置为表尾时的情况。
【算法描述】顺序表的插入运算
int insertList(List *L, int i, char content){ int k; //插入的位置不在表的范围 if(i < 0 || i >= L->len){ printf("插入位置不合法! "); return ERROR; } //考虑表满的情况 if(L->len == MAXSIZE){ printf("表已满!无法插入! "); return ERROR; }else if(i >= 0 && i <= L->len - 1){ //插入位置后的元素向后移动 for(k = L->len - 1; k >= i; k--){ L->data[k+1] = L->data[k]; } L->data[i] = content; //表长度加一 L->len++; printf("插入成功! "); print(L); return OK; } }
小结:设E为在长度为n的表中插入一元素所需移动元素的平均次数,假设为在第i个元素之前插入元素的概率,并假设在任何位置上插入的概率相等,即 , i = 1, 2, ……, n+1, 则有:
删除
线性表的删除操作指的是将表的第i个(1<=i<=n)个元素删去,使长度为n的线性表变成长度为n-1的线性表
【算法思想】:类似于插入操作,用顺序表作为线性表的存储结构时,由于结点的物理顺序必须和结点的逻辑顺序保持一致,空出第i个位置就要将原表位置的上的结点依次前移一个位置。
【算法描述】顺序表的删除运算
//删除 int deleteList(List *L, int i, char *content){ int k; //删除位置不合法则推出 if(i < 0 || (i >= L->len)){ printf("删除位置不合法! "); return ERROR; } //删除的表已空 if(L->len == 0){ printf("表已空! "); return ERROR; }else{ *content = L->data[i]; //前移 for(k = i; k <= L->len - 2; k++){ L->data[k] = L->data[k+1]; } //删除成功后表长度减一 L->len--; printf("删除成功! "); print(L); return OK; } }
小结:与插入算法类似,删除运算也要移动结点。设E为删除一个元素所需移动元素的平均移动次数, 为 删除第i个元素的概率,并假设在任何位置上删除的概率相等,即 ,i=1,2,……,n 。则有:
最后的汇总代码如下:
/* 线性表的顺序存储 基本操作:查找,插入,删除 */ #include<stdio.h> #include<string.h> #define MAXSIZE 100 #define OK 1 #define ERROR -1 typedef struct{ char data[MAXSIZE]; int len; //数据长度 }List; List* initList(List *L); //初始化 int getAsNum(List L, int num); //按序号查找 int getAsContent(List L, char content); //按内容查找 int insertList(List *L, int i, char content); //插入,在元素之前插入 int deleteList(List *L, int i, char *content); //删除 void print(List *L); //打印 //初始化顺序表 List* initList(List *L){ int num, i; char ch; //输入顺序表长度 printf("请输入顺序表长度(0<len<100): "); scanf("%d", &num); L->len = num; //输入数据 for(i = 0; i < L->len; i++){ getchar(); printf("请输入第 %d 个数:", i); scanf("%c", &ch); L->data[i] = ch; } return L; } //按序号查找 int getAsNum(List L, int num){ if(num < 0 || num > L.len - 1){ printf("查找失败,位置 %d 超过链表长度! ", num); return ERROR; }else{ printf("按序号查找成功,第 %d 个位置元素为 %c ", num, L.data[num]); return num; } } //按内容查找 int getAsContent(List L, char content){ unsigned int i = 0; while(i >= 0 || i <= L.len - 1){ //顺序扫描表,找到对应元素,或者扫描完则退出 if(L.data[i] != content){ i++; }else{ break; } } if(i <= L.len - 1){ //找到则输出并返回序号 printf("按内容查找成功,第 %d 个位置元素为 %c ", i, L.data[i]); return i; }else{ //未找到 printf("查找失败,没有你所找的元素! "); return ERROR; } } //插入,在元素之前插入 int insertList(List *L, int i, char content){ int k; //插入的位置不在表的范围 if(i < 0 || i >= L->len){ printf("插入位置不合法! "); return ERROR; } //考虑表满的情况 if(L->len == MAXSIZE){ printf("表已满!无法插入! "); return ERROR; }else if(i >= 0 && i <= L->len - 1){ //插入位置后的元素向后移动 for(k = L->len - 1; k >= i; k--){ L->data[k+1] = L->data[k]; } L->data[i] = content; //表长度加一 L->len++; printf("插入成功! "); print(L); return OK; } } //删除 int deleteList(List *L, int i, char *content){ int k; //删除位置不合法则推出 if(i < 0 || (i >= L->len)){ printf("删除位置不合法! "); return ERROR; } //删除的表已空 if(L->len == 0){ printf("表已空! "); return ERROR; }else{ *content = L->data[i]; //前移 for(k = i; k <= L->len - 2; k++){ L->data[k] = L->data[k+1]; } //删除成功后表长度减一 L->len--; printf("删除成功! "); print(L); return OK; } } //打印 void print(List *L){ int i; printf("===================顺序表如下=================== "); printf("共有 %d 个数据: ", L->len); for(i = 0; i < L->len; i++){ printf("%c ", L->data[i]); } printf(" "); } int main(void){ List L; int i, num, length, flag = 1; char ch, cha; //初始化 initList(&L); print(&L); //按序号查找 printf("请输入你要查找的元素序号:"); scanf("%d", &num); getchar(); getAsNum(L, num); //按内容查找 printf("请输入你要查找的元素的内容:"); scanf("%c", &ch); getchar(); getAsContent(L, ch); //插入元素 printf("请输入你要插入的内容(格式:addr_num data_char):"); scanf("%d %c", &num, &ch); getchar(); insertList(&L, num, ch); //删除元素 printf("请输入你要删除的位置(格式:addr_num):"); scanf("%d", &num); getchar(); deleteList(&L, num, &cha); return 0; }
执行结果:
线性表的链式存储
链表:链表使用一组任意的存储单元来存放线性表的结点,这组存储单元可以是连续的,也可以是非连续的,甚至是零散分布在内存的任何位置。
采用链式存储结构的线性表称为线性链表。从链接方式看,链表可分为单链表,循环链表,双链表(也叫循环双链表,双向链表)。从实现角度看可分为动态链表和静态链表。
结点:结点包括两个域:数据域和指针域。数据域存放数据,指针域指向其他结点的地址。两个域的数量视具体情况而定。
单链表和循环单链表有一个指针域,双链表有连个指针域。
单链表
单链表中每个结点的存储地址存放在其前驱结点的指针域中,由于线性表的第一个结点无前驱,通常设一个头指针header指向第一个结点。
定义:
单链表的存储结构如下:
typedef struct Node{ char ch; int len; //表长度 struct Node *next; }Node, *linkList;
创建
单链表的创建有两种:头插法和尾插法
和
代码在这里:
//尾插法建立表 linkList createFromTail(linkList L){ Node *s, *r; //r指针始终指向链表尾部 char ch; int flag = 1; r = L; printf("尾插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &ch); getchar(); if(ch == '#'){ //若输入 # 则退出 flag = 0; r->next = NULL; }else{ s = (linkList)malloc(sizeof(Node)); s->ch = ch; r->next = s; r = s; (L->len)++; flag = 1; } } print(L); return L; }
基本操作
其基本操作和顺序表一样:查找,插入,删除。
查找
这里也只讲按值查找
【算法思想】:从单链表的头指针指向的头结点出发,顺链逐个将结点值和给定值作比较。
【算法描述】
//按内容查找 linkList getAsContent(linkList L){ Node *p; char ch; int i = 1; p = L->next; printf(" 请输入查找内容:"); scanf("%c", &ch); getchar(); //遍历完表且未找到数据退出循环, 找到数据时退出函数 while(p != NULL){ if(p->ch == ch){ printf("按内容查找成功,第 %d 个位置的数据为 %c ", i, p->ch); return p; } p = p->next; i++; } //未找到数据 if(p == NULL){ printf("按内容查找失败!未在表中找到数据! "); } }
插入
【算法思想】:首先要找到插入位置i的前一个结点,并用指针pre指向它,然后申请新结点s,通过修改pre和s的指针域将新结点插入链表。
【算法描述】
//插入 linkList insertList(linkList L){ Node *pre, *s; int k, i; char ch; pre = L; k = 0; printf(" 请输入你要插入的位置和内容(格式: address content):"); scanf("%d %c", &i, &ch); getchar(); //插入位置不可能为负 if(i <= 0){ printf("插入失败!插入位置不合法!插入位置不能为负 "); return NULL; } ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环 while(pre != NULL && k < i - 1){ pre = pre->next; k++; } if(pre == NULL){ // 未找到插入位置(此时i大于表的长度) printf("插入失败!插入位置不合法!插入位置超出表的长度 "); return NULL; }else{ //找到插入位置并插入数据 s = (linkList)malloc(sizeof(Node)); s->ch = ch; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } }
删除
【算法思想】:通过计数方式找到删除位置的前一个结点并用pre指针指向它,然后删除结点并释放空间。
【算法描述】
//删除 linkList delList(linkList L){ Node *pre, *r; int k = 0, i; char *ch; pre = L; printf("请输入删除的数据的位置(格式:address):"); scanf("%d", &i); getchar(); //删除的位置必须合法 if(i > L->len || i<= 0){ printf("删除的位置超出了链表的长度! "); return; } // 找到删除位置退出 while(pre->next != NULL && k < i - 1){ pre = pre->next; k++; } //删除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("删除成功! "); print(L); return L; }
这是最后的完整算法:
/* 线性表的链式存储 基本操作:查找,插入,删除 */ #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct Node{ char ch; int len; //表长度 struct Node *next; }Node, *linkList; void initList(linkList *L); //初始化链表 linkList cteateFromeHead(linkList L); //头插法建立表 linkList createFromTail(linkList L); //尾插法建立表 linkList getAsNum(linkList L); //按序号查找 linkList getAsContent(linkList L); //按内容查找 linkList insertList(linkList L); //插入 linkList delList(linkList L); //删除 linkList print(linkList L); //查看链表 //初始化链表 void initList(linkList *L){ (*L) = (linkList)malloc(sizeof(Node)); (*L)->len = 0; (*L)->next = NULL; } //头插法建立表 linkList cteateFromeHead(linkList L){ Node *s; char ch; int flag = 1; printf("头插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &ch); getchar(); if(ch == '#'){ //若输入 # 则退出 flag = 0; }else{ s = (linkList)malloc(sizeof(Node)); s->ch = ch; s->next = L->next; L->next = s; (L->len)++; flag = 1; } } print(L); return L; } //尾插法建立表 linkList createFromTail(linkList L){ Node *s, *r; //r指针始终指向链表尾部 char ch; int flag = 1; r = L; printf("尾插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &ch); getchar(); if(ch == '#'){ //若输入 # 则退出 flag = 0; r->next = NULL; }else{ s = (linkList)malloc(sizeof(Node)); s->ch = ch; r->next = s; r = s; (L->len)++; flag = 1; } } print(L); return L; } //按序号查找 linkList getAsNum(linkList L){ int i, j; Node *p; p = L; j = 0; printf(" 请输入查找的序号: "); scanf("%d", &i); getchar(); //查找的序号不可能为负 if(i <= 0){ printf("输入不合法! "); return NULL; } //退出情况有两种:表遍历完毕没有找到数据 或 p指针指向目标结点 while((p->next != NULL) && (j < i)){ p = p->next; j++; } if(j == i){ //找到结点 printf("按序号查找成功, 序号 %d 的数据是 %c ", i, p->ch); return p; }else{ //未找到 printf("按序号查找失败!未在表中找到数据! "); return NULL; } } //按内容查找 linkList getAsContent(linkList L){ Node *p; char ch; int i = 1; p = L->next; printf(" 请输入查找内容:"); scanf("%c", &ch); getchar(); //遍历完表且未找到数据退出循环, 找到数据时退出函数 while(p != NULL){ if(p->ch == ch){ printf("按内容查找成功,第 %d 个位置的数据为 %c ", i, p->ch); return p; } p = p->next; i++; } //未找到数据 if(p == NULL){ printf("按内容查找失败!未在表中找到数据! "); } } //插入 linkList insertList(linkList L){ Node *pre, *s; int k, i; char ch; pre = L; k = 0; printf(" 请输入你要插入的位置和内容(格式: address content):"); scanf("%d %c", &i, &ch); getchar(); //插入位置不可能为负 if(i <= 0){ printf("插入失败!插入位置不合法!插入位置不能为负 "); return NULL; } ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环 while(pre != NULL && k < i - 1){ pre = pre->next; k++; } if(pre == NULL){ // 未找到插入位置(此时i大于表的长度) printf("插入失败!插入位置不合法!插入位置超出表的长度 "); return NULL; }else{ //找到插入位置并插入数据 s = (linkList)malloc(sizeof(Node)); s->ch = ch; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } } //删除 linkList delList(linkList L){ Node *pre, *r; int k = 0, i; char *ch; pre = L; printf("请输入删除的数据的位置(格式:address):"); scanf("%d", &i); getchar(); //删除的位置必须合法 if(i > L->len || i<= 0){ printf("删除的位置超出了链表的长度! "); return; } // 找到删除位置退出 while(pre->next != NULL && k < i - 1){ pre = pre->next; k++; } //删除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("删除成功! "); print(L); return L; } //查看链表 linkList print(linkList L){ Node *p; int i = 0; p = L->next; printf("==============查看链表数据为============== "); printf("共有 %d 个数据(注意序号是从头结点0开始,即第一个数据序号为 1) ", L->len); for(; i < L->len; i++){ printf("%c ", p->ch); p = p->next; } printf(" "); return L; } int main(void){ Node *L; initList(&L); //头插法 // cteateFromeHead(L); //尾插法 createFromTail(L); getAsNum(L); getAsContent(L); insertList(L); delList(L); return 0; }
运行结果如图:
循环单链表
定义
循环链表是一个首尾相接的链表。将单链表最后一个结点的指针域有NULL改为指向表头结点,就得到了单链形式的循环链表,并成为循环单链表。
对于循环单链表,若经常要在首尾两端进行操作,则可以设一个为指针在表尾(如下图中C)。
c语言定义如下:
typedef struct cNode{ char data; int len; //表长 struct cNode *next; }Node, *cNode;
创建
和前面一样,这里只讲尾插法创建
//尾插法建立表 cNode createFromTail(cNode L){ Node *s, *r; int i = 0, flag = 1; char data; r = L; printf("尾插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &data); getchar(); if(data != '#'){ s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = r->next; r->next = s; r = s; L->len++; }else{ printf("结束输入... "); flag = 0; } } r->next = L; print(L); return L; }
基本操作
查找
//按内容查找 cNode searchAsContent(cNode L){ Node *p; char data; int i = 1; p = L->next; printf(" 请输入查找内容:"); scanf("%c", &data); getchar(); //遍历完表且未找到数据退出循环, 找到数据时退出函数 while(p != L){ if(p->data == data){ printf("按内容查找成功,第 %d 个位置的数据为 %c ", i, p->data); return p; } p = p->next; i++; } //未找到数据 if(p == L){ printf("按内容查找失败!未在表中找到数据! "); } }
插入
//插入 cNode insertCNode(cNode L){ Node *pre, *s; int k, i; char data; pre = L->next; k = 1; printf(" 请输入你要插入的位置和内容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能为负 if(i <= 0){ printf("插入失败!插入位置不合法!插入位置不能为负 "); return NULL; } ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此时i大于表的长度) printf("插入失败!插入位置不合法!插入位置超出表的长度 "); return NULL; }else{ //找到插入位置并插入数据 ,注意:pre指向插入位置的前一个结点 s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } }
删除
//删除 cNode delList(cNode L){ Node *pre, *r; int k = 0, i; pre = L; printf("请输入删除的数据的位置(格式:address):"); scanf("%d", &i); getchar(); //删除的位置必须合法 if(i > L->len || i<= 0){ printf("删除的位置超出了链表的长度! "); return; } // 找到删除位置退出 while(pre->next != L && k < i - 1){ pre = pre->next; k++; } //删除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("删除成功! "); print(L); return L; }
完整代码:
/* 循环单链表 */ #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct cNode{ char data; int len; //表长 struct cNode *next; }Node, *cNode; cNode initCNode(cNode *L); //初始化 cNode createFromTail(cNode L); //尾插法建立表 cNode searchAsNum(cNode L); //按序号查找 cNode searchAsContent(cNode L); //按内容查找 cNode insertCNode(cNode L); //插入 cNode delList(cNode L); //删除 cNode print(cNode L); //查看链表 //初始化 cNode initCNode(cNode *L){ (*L) = (cNode)malloc(sizeof(Node)); (*L)->len = 0; (*L)->next = (*L); return (*L); } //尾插法建立表 cNode createFromTail(cNode L){ Node *s, *r; int i = 0, flag = 1; char data; r = L; printf("尾插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &data); getchar(); if(data != '#'){ s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = r->next; r->next = s; r = s; L->len++; }else{ printf("结束输入... "); flag = 0; } } r->next = L; print(L); return L; } //按序号查找 cNode searchAsNum(cNode L){ int i, j; Node *p; p = L->next; j = 1; printf(" 请输入查找的序号: "); scanf("%d", &i); getchar(); //查找的序号不可能为负 if(i <= 0){ printf("输入不合法! "); return NULL; } //单循环链表的到表尾的判断条件是 p != L while((p != L) && (j < i)){ p = p->next; j++; } if(j == i){ //找到结点 printf("按序号查找成功, 序号 %d 的数据是 %c ", i, p->data); return p; }else{ //未找到 printf("按序号查找失败!未在表中找到数据! "); return NULL; } } //按内容查找 cNode searchAsContent(cNode L){ Node *p; char data; int i = 1; p = L->next; printf(" 请输入查找内容:"); scanf("%c", &data); getchar(); //遍历完表且未找到数据退出循环, 找到数据时退出函数 while(p != L){ if(p->data == data){ printf("按内容查找成功,第 %d 个位置的数据为 %c ", i, p->data); return p; } p = p->next; i++; } //未找到数据 if(p == L){ printf("按内容查找失败!未在表中找到数据! "); } } //插入 cNode insertCNode(cNode L){ Node *pre, *s; int k, i; char data; pre = L->next; k = 1; printf(" 请输入你要插入的位置和内容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能为负 if(i <= 0){ printf("插入失败!插入位置不合法!插入位置不能为负 "); return NULL; } ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此时i大于表的长度) printf("插入失败!插入位置不合法!插入位置超出表的长度 "); return NULL; }else{ //找到插入位置并插入数据 ,注意:pre指向插入位置的前一个结点 s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } } //删除 cNode delList(cNode L){ Node *pre, *r; int k = 0, i; pre = L; printf("请输入删除的数据的位置(格式:address):"); scanf("%d", &i); getchar(); //删除的位置必须合法 if(i > L->len || i<= 0){ printf("删除的位置超出了链表的长度! "); return; } // 找到删除位置退出 while(pre->next != L && k < i - 1){ pre = pre->next; k++; } //删除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("删除成功! "); print(L); return L; } //查看链表 cNode print(cNode L){ Node *p; int i = 0; p = L->next; printf("查看链表数据为... "); printf("共有 %d 个数据(注意序号是从头结点0开始,即第一个数据序号为 1) ", L->len); for(; i < L->len; i++){ printf("%c ", p->data); p = p->next; } printf(" "); return L; } int main(void){ Node *L; printf("===============================循环单链表===================================== "); initCNode(&L); createFromTail(L); searchAsNum(L); searchAsContent(L); insertCNode(L); delList(L); printf("===============================循环单链表===================================== "); return 0; }
运行结果:
双向链表
定义
双向链表的的指针域在前面说过,它有两个指针域,一个指针域指向本结点的直接前驱,另一个则指向直接后继
定义:
typedef struct DNode{ char data; int len; struct DNode *prior; struct DNode *next; }DNode, *DList;
创建
//尾插法创建 DList createFromTail(DList L){ DNode *s, *r; int flag = 1; char data; r = L; printf("尾插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &data); getchar(); if(data == '#'){ //若输入 # 则退出 flag = 0; }else{ s = (DList)malloc(sizeof(DNode)); s->data = data; s->prior = r; s->next = L; r->next = s; r = s; L->prior = r; (L->len)++; flag = 1; } } printf("结束输入... "); print(L); return L; }
基本操作
查找
//按内容查找 DList searchAsContent(DList L){ DNode *p; char data; int i = 1; p = L->next; printf(" 请输入查找内容:"); scanf("%c", &data); getchar(); //遍历完表且未找到数据退出循环, 找到数据时退出函数 while(p != L){ if(p->data == data){ printf("按内容查找成功,第 %d 个位置的数据为 %c ", i, p->data); return p; } p = p->next; i++; } //未找到数据 if(p == L){ printf("按内容查找失败!未在表中找到数据! "); } }
插入
//插入 DList insertDList(DList L){ DNode *pre, *s; int k, i; char data; pre = L->next; k = 1; printf(" 请输入你要插入的位置和内容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能为负 if(i <= 0){ printf("插入失败!插入位置不合法!插入位置不能为负 "); return NULL; } ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此时i大于表的长度) printf("插入失败!插入位置不合法!插入位置超出表的长度 "); return NULL; }else{ //找到插入位置并插入数据 ,注意:pre指向插入位置的前一个结点 s = (DNode*)malloc(sizeof(DNode)); s->data = data; s->next = pre->next; pre->next->prior = s; s->prior = pre; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } }
删除
DList delDList(DList L){ DNode *p; int k = 1, i; p = L->next; printf("请输入删除的数据的位置(格式:address):"); scanf("%d", &i); getchar(); //删除的位置必须合法 if(i > L->len || i<= 0){ printf("删除的位置超出了链表的长度! "); return; } // 找到删除位置退出 while(p != L && k < i){ p = p->next; k++; } //删除操作 p->next->prior = p->prior; p->prior->next = p->next; //上面两步可以互换顺序 free(p); L->len--; printf("删除成功! "); print(L); return L; }
完整代码:
/* 双向链表 */ #include<stdio.h> #include<stdlib.h> typedef struct DNode{ char data; int len; struct DNode *prior; struct DNode *next; }DNode, *DList; DList initDList(DList *L); DList createFromTail(DList L); DList searchAsNum(DList L); DList searchAsContent(DList L); DList insertDList(DList L); DList delDList(DList L); DList print(DList L); //初始化 DList initDList(DList *L){ (*L) = (DList)malloc(sizeof(DNode)); (*L)->len = 0; (*L)->prior = (*L); (*L)->next = (*L); return *L; } //尾插法创建 DList createFromTail(DList L){ DNode *s, *r; int flag = 1; char data; r = L; printf("尾插法建立表,请输入数据并以'#'结束输入: "); while(flag){ printf("输入数据: "); scanf("%c", &data); getchar(); if(data == '#'){ //若输入 # 则退出 flag = 0; }else{ s = (DList)malloc(sizeof(DNode)); s->data = data; s->prior = r; s->next = L; r->next = s; r = s; L->prior = r; (L->len)++; flag = 1; } } printf("结束输入... "); print(L); return L; } //按序号查找 DList searchAsNum(DList L){ int i, j; DNode *p; p = L; j = 0; printf(" 请输入查找的序号: "); scanf("%d", &i); getchar(); //查找的序号不可能为负 if(i <= 0){ printf("输入不合法! "); return NULL; } //退出情况有两种:表遍历完毕没有找到数据 或 p指针指向目标结点 while((p->next != L) && (j < i)){ p = p->next; j++; } if(j == i){ //找到结点 printf("按序号查找成功, 序号 %d 的数据是 %c ", i, p->data); return p; }else{ //未找到 printf("按序号查找失败!未在表中找到数据! "); return NULL; } } //按内容查找 DList searchAsContent(DList L){ DNode *p; char data; int i = 1; p = L->next; printf(" 请输入查找内容:"); scanf("%c", &data); getchar(); //遍历完表且未找到数据退出循环, 找到数据时退出函数 while(p != L){ if(p->data == data){ printf("按内容查找成功,第 %d 个位置的数据为 %c ", i, p->data); return p; } p = p->next; i++; } //未找到数据 if(p == L){ printf("按内容查找失败!未在表中找到数据! "); } } //插入 DList insertDList(DList L){ DNode *pre, *s; int k, i; char data; pre = L->next; k = 1; printf(" 请输入你要插入的位置和内容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能为负 if(i <= 0){ printf("插入失败!插入位置不合法!插入位置不能为负 "); return NULL; } ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此时i大于表的长度) printf("插入失败!插入位置不合法!插入位置超出表的长度 "); return NULL; }else{ //找到插入位置并插入数据 ,注意:pre指向插入位置的前一个结点 s = (DNode*)malloc(sizeof(DNode)); s->data = data; s->next = pre->next; pre->next->prior = s; s->prior = pre; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } } //删除 DList delDList(DList L){ DNode *p; int k = 1, i; p = L->next; printf("请输入删除的数据的位置(格式:address):"); scanf("%d", &i); getchar(); //删除的位置必须合法 if(i > L->len || i<= 0){ printf("删除的位置超出了链表的长度! "); return; } // 找到删除位置退出 while(p != L && k < i){ p = p->next; k++; } //删除操作 p->next->prior = p->prior; p->prior->next = p->next; //上面两步可以互换顺序 free(p); L->len--; printf("删除成功! "); print(L); return L; } //查看表 DList print(DList L){ DNode *p; int i = 0; p = L->next; printf("查看链表数据为... "); printf("共有 %d 个数据(注意序号是从头结点0开始,即第一个数据序号为 1) ", L->len); for(; i < L->len; i++){ printf("%c ", p->data); p = p->next; } printf(" "); return L; } int main(void){ DNode *L; printf("===============================双向链表===================================== "); initDList(&L); createFromTail(L); searchAsNum(L); searchAsContent(L); insertDList(L); delDList(L); printf("===============================双向链表===================================== "); return 0; }
运行结果:
顺序表和链表的比较总结
在数据结构绪论中就有提及算法的性能评价,既要从时间方面考虑(时间复杂度),又要从空间方面考虑(空间复杂度)。线性表在物理结构上的存储造成了顺序表和链表性能上的差异:顺序表使用一组连续的物理地址存储(逻辑上通常为数组,能够随机存取),而链表的物理存储地址是任意的(不能随机存取)。如此一来,线性表的基本操作会受到影响。
基于时间
适合顺序表的情况:因为顺序表通常是用数组表示决定了顺序表能够随机存储,所以当线性表的操作主要是进行查找而很少做插入和删除操作时应用顺序表。
适合链表的情况:与顺序表的情况想反,需要频繁的进行插入或删除操作的线性表应用链表。
基于空间
存储密度,是指结点数据本身所占的存储量和整个结点结构所占的存储量之比。
由前面提到顺序表和链表的物理存储地址可知:顺序表是静态分配的,链表是动态分配。静态分配的一个缺点就只,分配时空间有多大就是多大,存储规模是确定的,若数据元素无法用完静态分配的空间,就会造成浪费,存储密度小,空间利用率低。而动态分配则不会有这种情况。
因此,当线性表的长度变化不大,易于事先确定其存储规模时宜采用顺序表,反之应采用链表。
线性表链式存储方式比较
链表名称/操作名称 | 找表中首元素结点 | 找表尾元素结点 | 找p结点的前驱结点 |
带头结点的单链表L | L->next 时间耗费O(1) | 一重循环时间耗费O(n) | 无法找到 |
带头结点的循环单链表L | L->next 时间耗费O(1) | 一重循环时间耗费O(n) | 顺p结点的next域可以找到,时间耗费O(n) |
带尾指针的循环单链表R | R->next ->next 时间耗费O(1) | 一重循环时间耗费O(1) | 顺p结点的next域可以找到,时间耗费O(n) |
到头结点的双向链表L | L->next 时间耗费O(1) | 一重循环时间耗费O(1) | p->prior就是p的前驱结点,时间耗费O(1) |