一、链表概述
在线性表这种数据结构中,除了有按照线性存储的顺序表的数据结构外,还有一种是以链式来存储线性表的数据结构,即为链表。它不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上也相邻;它是通过“链”建立起数据元素之间的逻辑关系。
链表是指通过一组任意的存储单元来存储线性表中的数据元素。对每个链表结点,除存放元素自身的信息(data数据域)外,还需要存放一个指向其后继的指针(next指针域)。通常用头指针(head)来标识一个单链表。
单链表的逻辑结构如下:
typedef struct Node{
int data;
struct Node *next;
}Node, *LinkedList;
二、链表的基本操作
链表的基本操作主要包括插入、删除、遍历、查找等。
1、链表的插入
链表的插入主要分为三种方法——头插法、尾插法以及从指定位置插入。
头插法是从一个空表开始,生成新结点(包含数据),将新结点插入到当前链表的表头,即头结点之后。该方法生成的链表中结点的次序和输入数据的顺序不一致(为了一致,可以使用尾插法)。
尾插法是将新结点插入到当前链表的表尾,为此需增加一个尾指针,使其始终指向当前链表的尾结点。
以上两种方法是在一个空表的基础上创建了一个单链表。但在日常应用中,在使用链表时,常常会遇到需要在某一位置插入。具体方法如下:
1). 找到链表中要插入的位置。
2). 令待插入结点的 next 指针指向插入位置的当前结点。
3). 令插入位置之前的当前结点的 next 指针指向待插入结点。
对于上述方法,具体的代码实现如下:
LinkedList insert(LinkedList head, Node *node, int index) {
if (head == NULL) { //头指针为空指针,链表为空
if (index != 0) {
printf("failed\n");
return head;
}
head = node;
printf("success\n");
return head;
}
if (index == 0) { //插入位置为链表首位
node->next = head;
head = node;
printf("success\n");
return head;
}
Node *current_node = head;
int count = 0;
while (current_node->next != NULL && count < index - 1) { //寻找插入位置的前一个位置
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
printf("success\n");
return head;
}
printf("failed\n");
return head;
}
2、链表的删除操作
在链表中,我们可以通过调用删除方法来删除指定位置的结点。
具体函数定义为delete(index),表示为将链表中下标为index 的元素删除。
具体方法如下:
1). 从表头遍历找到要删除的位置。
2). 令删除位置前一个结点的next指针指向待删除位置后一个结点。
3). 删除结点。
代码实现如下:
LinkedList delete(LinkedList head, int index) {
if(head == NULL){ //链表为空
printf("failed\n");
return head;
}
Node *current_node = head;
int count = 0;
if(index == 0){ //删除的结点为头结点
head = head->next;
free(current_node);
printf("success\n");
return head;
}
while(current_node->next != NULL && count < index - 1){ //查找目标位置的前一位置
current_node = current_node->next;
count++;
}
if(count == index - 1 && current_node->next != NULL){
Node *delete_node = current_node->next;
current_node->next = delete_node->next;
free(delete_node);
printf("success\n");
return head;
}
printf("failed\n");
return head;
}
3、链表的遍历操作
链表的遍历操作,顾名思义,就是将链表从表头到表尾访问一遍。实现起来比较简单,具体实现如下:
void output(LinkedList head) {
if (head == NULL) {
return;
}
Node *current_node = head;
while (current_node != NULL) {
printf("%d ", current_node->data);
current_node = current_node->next;
}
printf("\n");
}
4、链表的查找操作
链表的查找操作,是从表头开始向后遍历,找到了目标结点则返回它的位置。读者可自行实现具体的过程。
三、链表的相关应用
1、链表的翻转操作
链表的翻转操作,顾名思义,链表的反转就是一个将整个链表按倒序排列(即按照从表尾到表头的顺序访问链表)的重要操作。
具体操作方法如下:
1). 定义一个用于遍历的指针,初始指向头结点后一个结点。
2). 让头结点的 next 指针置空。
3). 从当前遍历指针所指的结点开始遍历链表,将遍历到的结点 next 指针指向头结点。遍历过程中借助另外一个指针保存下一个遍历到的结点。
4). 重复步骤 3) 直至表尾,此时新的链表就是原链表反转后的链表。
具体代码实现如下:
LinkedList reverse(LinkedList head) {
if(head == NULL){return head;}
Node *next_node,*current_node;
current_node = head->next;
head->next = NULL;
while(current_node != NULL){
next_node = current_node->next;
current_node->next = head;
head = current_node;
current_node = next_node;
}
return head;
}
2、约瑟夫环
约瑟夫环问题是基于循环链表实现的,关于循环链表的操作,读者可自行查阅了解。
约瑟夫问题来源于犹太历史学家约瑟夫,他和他的一位朋友与另外三十九名犹太人为了躲避罗马人藏在了一个山洞中,三十九位犹太人决定宁愿自杀也不能被抓到。他们商议围成一个圈,从某一个人开始数1,每数到第3 的人必须自杀然后再从他之后的人继续数 1。这时候,约瑟夫把朋友和自己安排在了第16 和第31 的位置,最终,当其它三十九名犹太人都自杀之后,他们两个躲过一劫。
约瑟夫环中的删除操作实现方法如下:
1). 定义一个遍历指针,初始指向 head,并让 head 指向空地址避免操作结束后变为野指针。
2). 找到遍历指针往后数的第 n 次所指向的结点。
3). 输出该结点的数据,更新遍历指针,然后删除该结点。
4). 重复操作 2 直至只剩下最后一个结点。
5). 输出最后一个结点并删除。
//请在下面实现循环链表的创建过程
LinkedList insert(LinkedList head, Node *node, int index) {
if (head == NULL) {
if (index != 0) {
return head;
}
head = node;
head->next = head;
return head;
}
if (index == 0) {
node->next = head->next;
head->next = node;
return head;
}
Node *current_node = head->next;
int count = 0;
while (current_node != head && count < index - 1) {
current_node = current_node->next;
count++;
}
if (count == index - 1) {
node->next = current_node->next;
current_node->next = node;
}
if (node == head->next) {
head = node;
}
return head;
}
// 请在下面实现输出函数 output_josephus
void output_josephus(LinkedList head,int m){
Node *current_node = head;
head = NULL;
while(current_node->next != current_node){
for(int i=1;i<m;i++){
current_node = current_node->next;
}
printf("%d ",current_node->next->data);
Node *delete_node = current_node->next;
current_node->next = current_node->next->next;
free(delete_node);
}
printf("%d\n",current_node->data);
free(current_node);
}