说明
什么是链表
以下来自于维基百科
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(“links”)。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的访问和操作。程序语言或面向对象语言,如C/C++和Java依靠易变工具来生成链表。
链表定义
链表是用一组地址任意的存储单元来存储数据,每个存储单元分散在内存的任意地址上,存储单元之间用指针连接。
链表分类
- 单链表
- 带头结点单链表(头结点不存放元素)
- 不带头结点的单链表
- 循环链表
- 约瑟夫环
- 双向链表
- 带头结点的双向链表
- 不带头结点的双向链表
单链表及其相关操作
注:为了统一,以下均是不带头结点的单链表
单链表结点定义
typedef struct node{
int data;
struct node *next;
}LNode,*LinkList;
建立一个长度为 N 的单链表(尾插法)
LinkList CreatLinkList(int n) {
/*建立一个长度为n的链表*/
LinkList p, r, list = NULL;
int e;
int i;
for (i = 1; i <= n; i++) {
scanf("%d", &e); /*获取链表结点中的数据元素*/
p = (LinkList)malloc(sizeof(LNode)); /*分配一个新的链表结点*/
p->data = e;
p->next = NULL;
if (list == NULL) {
list = p; /*list指向第一个结点,list是头指针*/
} else {
r->next = p; /*将新结点连接到链表的尾部*/
}
r = p; /*指针r始终指向链表的最后一个结点*/
}
return list; /*返回链表的头指针*/
}
建立一个长度为 N 的单链表(头插法)
LinkList createList() {
LinkList list = (LinkList)malloc(sizeof(LNode));
list->next = NULL;
LinkList pre = list, last;
int data;
scanf("%d", &data);
while(data) {
last = (LinkList)malloc(sizeof(LNode));
last->data = data;
last->next = NULL;
pre->next = last;
pre = last;
scanf("%d", &data);
}
return list;
}
遍历打印单链表
void printLink(LinkList list) {
while (list != NULL) {
printf("%3d", list->data); /*打印出每个结点中的数据data*/
list = list->next;
}
printf("\n");
}
单链表长度
int lenLinkList(LinkList list) {
int len = 0;
while(list) {
len++;
list = list->next;
}
return len;
}
删除单链表中指针 q 指向的结点
void delLink(LinkList *list, LinkList q) {
LinkList r;
if (q == *list) {
*list = q->next; /*如果q指向的结点即为第1个结点,则需要修改list的值*/
free(q); /*释放被删除结点的空间*/
} else {
r = *list;
while ((r->next != q) && (r->next != NULL)) {
r = r->next; /*通过循环找到q所指结点的前驱结点*/
}
if (r->next != NULL) {
r->next = q->next; /*删除q所指向的结点*/
free(q); /*释放被删除结点的空间*/
}
}
}
在递增单链表中插入元素
void insertListInOrder(LinkList *list,int e)
{
/*向按值有序(递增序列)的链表list中插入包含元素e的结点*/
LinkList p, q, r;
q = *list;
p=(LinkList)malloc(sizeof(LNode)); /*生成一个新结点,由p指向它*/
p->data=e; /*向该结点的数据域赋值e*/
if (*list == NULL || e < (*list)->data) {
p->next = *list;
*list = p;
} else {
while(q != NULL && e >= q->data) { /*循环找到插入结点的位置*/
r = q; /*r指向q的前驱结点*/
q = q->next; /*q指针向后移动*/
}
p->next= q; /*插入新的结点*/
r->next = p;
}
}
销毁单链表
void deleteLinkList(LinkList *list) {
LinkList p = *list; /*p指向第一个结点*/
while (p != NULL) {
*list = p ->next; /* list指向p的下一个结点 */
free(p); /*释放掉p指向结点的内存空间*/
p = *list; /*p再指向第一个结点*/
}
}
将两个有序的单链表合并
LinkList MergeList(LinkList list1, LinkList list2) {
LinkList list3;
LinkList p = list1, q = list2;
LinkList r;
if (list1->data <= list2->data) {
list3 = list1; /*list1的第一个元素最小,list3指向它*/
r = list1; /*指针r指向list1的第一个元素*/
p = list1->next; /*p指向list1的第二个元素*/
} else {
list3 = list2; /*list2的第一个元素最小,list3指向它*/
r = list2; /*指针r指向list2第一个元素*/
q = list2->next; /*q指向list2的第二个元素*/
}
while (p!=NULL && q!=NULL) {
if (p->data<=q->data) { /*若当前p指向的结点值不大于q指向的结点值*/
r->next = p; /*将p所指向的结点链接到r所指向的结点的后面*/
r = p; /*指针后移*/
p = p->next; /*指针后移*/
} else {
r->next = q; /*将所指向的结点链接到r所指向的结点的后面q*/
r = q; /*指针后移*/
q = q->next; /*指针后移*/
}
}
r->next = p?p:q; /*插入剩余结点*/
return list3; /*返回合并后的链表list3,即链表首地址*/
}
单链表逆序
void reverseLinkList(LinkList *list) {
LinkList p,q ,r;
p = *list;
q = NULL;
while (p != NULL) {
r = q;
q = p;
p = p->next;
q->next = r;
}
*list = q;
}
单链表的倒数第 K 个元素
LinkList findKToLastElem(LinkList list, int k) {
LinkList p, q;
int i;
p = list; /*p指向链表的第一个结点*/
for (i=1; i<k && p != NULL; i++) {
p = p->next; /*循环完毕时p指向第k个结点*/
}
if (i == k && p != NULL) {
q = list; /*如果p指向了第k个结点,q指向第一个结点*/
} else {
return NULL; /*否则链表的长度小于k*/
}
while(p->next!=NULL) {
p = p->next; /*指针p和q同步向后移动,直到p遍历完链表*/
q = q->next;
}
return q; /*q指向了倒数第k个结点,返回之*/
}
单链表排序(换值排序)
void sortLinkList(LinkList list, int len) {
for(int i = 0; i < len - 1; i++) {
LinkList cur = list;
for(int j = 0; j < len - i; j++) {
if(cur->data > cur->next->data) {
int tmp = cur->data;
cur->data = cur->next->data;
cur->next->data = tmp;
}
cur = cur->next;
}
}
}
单链表排序(换指针排序)
void sortLinkListByPtr(LinkList list, int len) {
LinkList subList p, q, tmp;
int i, j;
for(i = 0; i < len - 1; i++) {
subList = list;
p = list->next;
q = p->next;
for(j = 0; j < len - 1 - i; j++) {
if(p->data > q->data) {
subList->next = p->next;
p->next = q->next;
q->next = p;
tmp = p;
p = q;
q = tmp;
}
subList = subList->next;
p = p->next;
q = q->next;
}
}
}
循环链表
定义
循环链表是一种特殊的单链表,其最后一个结点的指针域指向单链表的头结点或者直接指向第一个元素结点
循环链表结点定义
typedef struct node{
ElemType data; /*数据域*/
struct node *next ; /*指针域*/
}LNode,*LoopLinkList;
创建一个循环链表
LoopLinkList CreatLoopLinkList(int n) {
LoopLinkList list, p, r;
ElemType e;
int i;
Get(&e);/*得到第一个元素结点数据*/
r = list = (LoopLinkList)malloc(sizeof(LNode));/*创建第一个结点*/
list->next = list;/*指针指向自身*/
list->data = e;/*复制第一个结点的数据元素*/
for(int i = 1; i <= n - 1; i++) {
Get(&e);
p = (LoopLinkList)malloc(sizeof(LNode));
p->data = e;
p->next = list;/*指向链表的第一个结点*/
r->next = p;/*将新结点连入循环链表*/
r = r->next;/*指针 r 后移*/
}
return list;
}
循环链表结点的插入(同上)
略
循环链表结点的删除(同上)
略
约瑟夫环问题
编号为 1~ N 的 N 个人顺时针围成一个圈,每人都持有一个密码(正整数),开始时任选一个正整数作为报数上限值 M,从编号为 1 的人开始顺时针方向从 1 报数,报到 M 时停止,报 M 的人出列,并将他手中的密码作为新的报数上限 M ,从他顺时针方向的下一个人开始从 1 报数,如此下去,直至所有人出列为止。编写一个程序,求这些人的出列顺序。
#include "stdio.h"
#include "malloc.h"
typedef struct node {
int id; /*成员编号*/
int key; /*密码*/
struct node *next ; /*指针域*/
} LNode, *LoopLinkList;
LoopLinkList CreatJosephRing(int n) {
LoopLinkList list, p, r;
int e;
int i;
scanf("%d", &e); /*得到第一个元素结点数据*/
r = list = (LoopLinkList) malloc(sizeof(LNode)); /*创建第一个结点*/
list->next = list; /*指针指向自身*/
list->key = e; /*复制第一个结点的数据元素*/
list->id = 1;
for(i = 2; i <= n; i++) {
/*循环创建后续的n-1个结点*/
scanf("%d", &e);
p = (LoopLinkList)malloc(sizeof(LNode));
p->key = e;
p->id = i;
p->next = list; /*指向链表第一个结点*/
r->next = p; /*将新结点连入循环链表*/
r = r->next; /*指针r后移*/
}
return list;
}
main() {
LoopLinkList list = NULL, p = NULL, q = NULL;
int n, m, i;
printf("Input the number of the people in Joseph Ring\n");
scanf("%d", &n);
printf("Input the password of the people\n");
list = CreatJosephRing(n);
printf("Input the first Maximum Number M\n");
scanf("%d", &m);
printf("The order of leaving Joseph Ring\n");
q = p = list;
while(q->next != list) {
q = q->next; /*q指向p的前驱结点*/
}
while(p != q) {
for(i = 0; i < m - 1; i++) {
p = p->next;
q = q->next;
}
printf("%3d", p->id); /*输出出队者的编号*/
m = p->key; /*下一次的报数上限*/
q->next = p->next; /*删除结点*/
free(p); /*释放删除的结点空间*/
p = q->next;
}
printf("%3d", p->id);
free(p);
list = p = q = NULL;
printf("\n");
getchar();
getchar();
}
判断链表是否为循环链表
/*-----------------------------16-19.c -----------------------------*/
#include "stdio.h"
#include "malloc.h"
typedef struct node {
int data;
struct node *next;
} LNode, *LinkList;
LinkList CreatLinkList(int n) {
/*建立一个长度为n的链表*/
LinkList p, r, list = NULL;
int e;
int i;
for (i = 1; i <= n; i++) {
scanf("%d", &e); /*获取链表结点中的数据元素*/
p = (LinkList)malloc(sizeof(LNode)); /*分配一个新的链表结点*/
p->data = e;
p->next = NULL;
if (list == NULL) {
list = p; /*list指向第一个结点,list是头指针*/
} else {
r->next = p; /*将新结点连接到链表的尾部*/
}
r = p; /*指针r始终指向链表的最后一个结点*/
}
return list; /*返回链表的头指针*/
}
LinkList CreatLoopLinkList(int n, int k) {
/*建立一个长度为n的链表*/
LinkList p, r, q, list = NULL;
int e;
int i;
for (i = 1; i <= n; i++) {
scanf("%d", &e); /*获取链表结点中的数据元素*/
p = (LinkList)malloc(sizeof(LNode)); /*分配一个新的链表结点*/
p->data = e;
p->next = NULL;
if (list == NULL) {
list = p; /*list指向第一个结点,list是头指针*/
} else {
r->next = p; /*将新结点连接到链表的尾部*/
}
r = p; /*指针r始终指向链表的最后一个结点*/
}
q = list;
for (i = 0 ; i < k - 1; i++) {
q = q->next;
}
r->next = q;
return list; /*返回链表的头指针*/
}
int isLoopLinkList(LinkList list) {
LinkList fast, slow;
fast = list->next;
slow = list;
while (1) {
if (fast == NULL || fast->next == NULL) {
return 0;
} else if (fast == slow || fast->next == slow) {
return 1;
} else {
fast = fast->next->next;
slow = slow->next;
}
}
}
main() {
LinkList list;
printf("Create a 10 elems linklist without loop\n");
list = CreatLinkList(10);
printf("This linklist is %s\n", isLoopLinkList(list) == 1 ? "LoopLinkList" : "NOT LoopLinkList");
printf("Create a 10 elems loop linklist\n");
list = CreatLoopLinkList(10, 5);
printf("This linklist is %s\n", isLoopLinkList(list) == 1 ? "LoopLinkList" : "NOT LoopLinkList");
getchar();
getchar();
}
双向链表
注:为了统一,以下均是带头结点的双向链表
双向链表结点定义
与单链表的结点不同,双向链表的每个结点一般包含 3 个域,除了数据域外还设置两个指针域,一个指针指向其直接前驱结点,另一个指针指向其直接后继结点。
双向链表结点表示
typedef struct dnode {
int data;
struct dnode *pre;
struct dnode *next;
} DNode;
双向链表的创建(尾插法)
DNode *creatDList() {
DNode *head = (DNode *)malloc(sizeof(DNode));
DNode *tail = head; //尾插法的一个特点是,必须有指针,指向最后一个节点
DNode *cur = NULL;
head->next = NULL;
head->pre = NULL;
int data;
scanf("%d", &data);
while(data) {
cur = (DNode *)malloc(sizeof(DNode));
cur->data = data;
tail->next = cur;
cur->pre = tail;
tail = cur;
scanf("%d", &data);
}
//完成回环
cur->next = head;
head->pre = cur;
return head;
}
双向链表的创建(头插法)
DNode *creatDList() {
DNode *head = (DNode *)malloc(sizeof(DNode));
DNode *cur = NULL;
head->next = head;
head->pre = head;
int data;
scanf("%d", &data);
while(data) {
cur = (DNode *)malloc(sizeof(DNode));
cur->data = data;
cur->next = head->next;
cur->pre = head;
head->next = cur;
cur->next->pre = cur;
scanf("%d", &data);
}
return head;
}
双向链表插入元素
void insertDList(DNode *head, int insertData) {
DNode *cur;
cur = (DNode *)malloc(sizeof(DNode));
cur->data = insertData;
cur->next = head->next;
cur->pre = head;
head->next = cur;
cur->next->pre = cur;
}
双向链表的遍历(后向遍历)
void traverseDList(DNode *head) {
DNode *phead = head->next;
while (phead != head) {
printf("%d ", phead->data);
phead = phead->next;
}
}
双向链表的长度
int lenDList(DNode *head) {
int len = 0;
DNode *phead = head->next;
while(phead != head) {
len++;
phead = head->next;
}
return len;
}
双向链表的查找(单向查找)
DNode *searchDList(DNode *head, int find) {
DNode *phead = head->next;
while(phead != head) {
if(phead->data == find)
return phead;
phead = head->next;
}
return NULL;
}
双向链表的查找(双向查找)
DNode *searchList(DNode *head, int find) {
DNode *pClock = head->next;
DNode *pAntiClock = head->pre;
while (pAntiClock != pClock->pre) {
if(pClock->data == find)
return pClock;
if(pAntiClock->data == find)
return pAntiClock;
if(pClock == pAntiClock) //交错而过 一定包含相等的逻辑在里面
return NULL;
pClock = pClock->next;
pAntiClock = pAntiClock->pre;
}
return NULL;
}
删除双向链表结点
void deleteNodeDList(DNode *pfind) {
if(pfind == NULL)
return;
else {
pfind->pre->next = pfind->next;
pfind->next->pre = pfind->pre;
free(pfind);
}
}
双向链表的排序
void sortDList(DNode *head, int n) {
DNode *p, *q;
for(int i = 0; i < n - 1; i++) {
p = head->next;
q = p->next;
for(int j = 0; j < n - 1 - i; j++) {
if(p->data > q->data) {
p->data = p->data ^ q->data;
q->data = p->data ^ q->data;
p->data = p->data ^ q->data;
}
p = p->next;
q = q->next;
}
}
}
双向链表的销毁
void destroyDList(DNode *head) {
head->pre->next = NULL;
DNode *pre = head;
while(head != NULL) {
head = head->next;
free(pre);
pre = head;
}
}