C语言数据结构基础——链表
链表是线性表的一种,每个节点里存储了数据域和指针域,其中指针域中存储指向下一个节点的指针。相比顺序表,链表在插入和删除操作上更方便。链表有很多不同类型:单向链表、双向链表、循环链表等。
链表定义
我们把链表想象成火车,火车头便是链表的表头,每节车厢就是链表的元素,车厢里载的人和物就是元素的数据域,连接车厢的部件就是元素的指针。
由此,我们可以得出链表的一个特点:元素之间前后依赖,串联而成。相比于数据域成片的顺序表链表的元素不能随机访问。链表中元素前后连接是单一的,即元素前面和后面不会出现多个元素相连的情况。
链表基本操作的实现
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
typedef struct List {
Node *head; // 头指针地址
int length; // 链表长度
} List;
/*
* 链表初始化操作
* @return 链表指针
* */
List *init_list() {
List *l = (List *)malloc(sizeof(List));
l->head = NULL;
l->length = 0;
return l;
}
/*
* 插入操作,时间复杂度O(n)
* @param l 待操作链表
* @param ind 待插入坐标
* @param val 待插入元素值
* */
int insert(List *l, int ind, int val) {
if(l == NULL) return 0; // 链表未初始化
if(ind < 0 || ind > l->length) return 0; // 判断坐标是否合法
Node *node = (Node *)malloc(sizeof(Node)); // 新建一个链表结点
node->data = val;
node->next = NULL;
if(ind == 0) { // 坐标为0,需要更新头指针
node->next = l->head;
l->head = node;
}else {
Node *p = l->head; // 新建一个临时指针指向链表头
while(--ind) p = p->next; // 移动指针到ind前一个位置
node->next = p->next; // 将当前结点的指针指向原本ind位置
p->next = node; // ind的前一个结点指向当前新结点
}
l->length++; // 更新链表长度
return 1;
}
/*
* 删除操作 时间复杂度O(n)
* @param l待操作链表
* @param ind 待删除元素的坐标
* */
int delete(List *l, int ind) {
if(l == NULL) return 0; // 链表未初始化
if(ind < 0 || ind >= l->length) return 0; // 判断坐标是否合法
Node *delete_node;
if(ind == 0) { // 坐标为0,需要更新头指针
delete_node = l->head;
l->head = l->head->next;
}else {
Node *p = l->head; // 新建一个临时指针指向链表头
while(--ind) p = p->next; // 移动指针到ind前一个位置
delete_node = p->next;
p->next = delete_node->next; // ind前一个位置的指针指向ind后一个位置
}
free(delete_node); // 释放指定结点
l->length--; // 更新链表长度
return 1;
}
/**
* 反转操作 时间复杂度 O(n)
* @param l待操作链表
* */
void reverse(List *l) {
if(l == NULL || l->length < 2) return; // 链表未初始化或者,长度在1以内不需要反转
Node *p = l->head; // 定义一个结点指向链表头
l->head = NULL; // 将头结点都链表上拆下来
while(p) { // 头结点指向当前结点的后两个结点
Node *temp = p->next; // 定义一个临时结点指向当前结点的下一个
p->next = l->head; // 将当前结点从链表上取下来
l->head = p; // 当前结点更新为头结点
p = temp; // 移动当前指向
}
return;
}
/*
* 清理链表 时间复杂度 O(n)
* @param l待操作链表
* **/
void clear(List *l) {
if(l == NULL) return; // 判断链表是否已经初始化
Node *p = l->head; // 定义一个结点指向链表头
while(p) {
Node *temp = p->next; // 定义临时结点指向后一个结点
free(p); // 释放当前结点
p = temp; // 更新当前链表
}
free(l);
}
/*
* 查询链表 时间复杂度 O(n)
* @param l待操作链表
* @param val 待查找的值
* */
int search(List *l, int val) {
if(l == NULL) return -1; // 判断链表是否已经初始化
Node *p = l->head; // 定义一个结点指向链表头
int i = 0;
while(p) {
if(p->data == val) return i;
p = p->next;
i++;
}
return -1;
}
/*
* 遍历链表 时间复杂度 O(n)
* @param l待操作链表
* **/
void output(List *l) {
if(l == NULL) return; // 判断链表是否已经初始化
Node *p = l->head; // 定义一个结点指向链表头
printf("List:[");
while(p) {
printf("%d ", p->data);
p = p->next;
}
printf("]\n");
}
int main() {
srand(time(0));
#define MAX_OP 20
List *l = init_list();
for(int i = 0; i < MAX_OP; i++) {
int op = rand() % 4;
int val = rand() % 100;
int ind = rand() % (l->length + 3);
if(op) {
int res = insert(l, ind, val);
printf("insert %d at %d to List = %d\n", val, ind, res);
}else {
int res = delete(l, ind);
printf("erase a item at %d from List = %d\n", ind, res);
}
output(l), printf("\n");
}
reverse(l);
output(l);
clear(l);
#undef MAX_OP
return 0;
}
循环链表的基本操作
循环链表,相比单链表,循环链表不同的是它将最后一个结点的指针指向了头结点,这样的结构使得链表更加灵活方便。循环链表里没有空指针,所以在判断结束条件时,不再是判断指针是否为空,而是判断指针是否等于某固定指针。另外,在单链表里,一个节点只能访问到它后面的结点,而在循环链表里它可以访问到所有的结点。
/*************************************************************************
> File Name: 2.linklist_loop.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年03月29日 星期一 11时31分10秒
> 循环链表的基本操作
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
/*链表节点定义*/
typedef struct Node {
int data;
struct Node *next;
} Node;
/*链表定义*/
typedef struct List{
Node *head;
int length;
} List;
/*
* 初始化链表
* */
List *init() {
List *l = (List *)malloc(sizeof(List)); // 动态申请指针域
l->head = NULL; // 默认链表指针指向空
l->length = 0;
}
/*
* 插入元素 时间复杂度O(n)
* @param l: 要操作的链表指针
* @param ind: 要插入的下标点
* @param val: 要插入元素的值
* */
int insert(List *l, int ind, int val) {
if(l == NULL) return 0; // 判断链表是否已经初始化
if(ind < 0 || ind > l->length) return 0; // 判断要插入的下标是否合法
Node *node = (Node *)malloc(sizeof(Node)); // 动态申请一个节点,并初始化
node->data = val;
node->next = NULL;
/*判断链表是否为空*/
if(l->head == NULL) {
l->head = node;
l->head->next = node;
}else {
Node *p = l->head;
int index = ind;
while(index--) p = p->next; // 将链表移动到指定位置的前一个下标
node->next = p->next;
p->next = node;
if(ind == l->length) l->head = node; // 若插入坐标为最后一位,更新头节点
}
l->length++;
return 1;
}
/*
* 删除元素 时间复杂度为O(n)
* @param l: 要操作的链表指针
* @param ind: 要删除的下标点
* **/
int delete(List *l, int ind) {
if(l == NULL) return 0; // 判断链表是否已经初始化
if(ind < 0 || ind >= l->length) return 0; // 判断要插入的下标是否合法
Node *p = l->head, *delete_node;
if(l->length == 1) {
delete_node = l->head;
l->head = NULL;
}else {
while(ind--) p = p->next; // 将链表移动到指定位置的前一个下标
delete_node = p->next; // 记录要删除的节点地址
p->next = delete_node->next;
if(delete_node == l->head) l->head = delete_node->next;
}
free(delete_node);
l->length--;
return 1;
}
/*
* 反转链表 时间复杂度O(n)
* @param l: 要操作的链表指针
* **/
void reverse(List *l) {
if(l == NULL || l->length < 2) return; // 链表为空或者长度小于2什么都不用做
Node *p,*head = l->head; // 定义一个节点指向链表尾部
Node *begin = p = head->next;
l->head = NULL; // 把头节点从循环链表上拆下来
Node *temp = NULL;
while(p != head){ // 从头到位循环到尾节点前一个
temp = p->next;
p->next = l->head;
l->head = p;
p = temp;
}
// 链表变成一个单链表及一个节点head。
head->next = l->head; // temp等于前头结点,当前的尾节点
begin->next = head;
l->head = begin;
}
/*
* 清理链表 时间复杂度O(n)
* @param l: 要操作的链表指针
* **/
void clear(List *l) {
if(l == NULL) return; // 判断链表是否已经初始化
if(l->head == NULL) {
free(l);
return;
}
Node *p,*head = l->head; // 定义一个节点指向链表头
p = head->next;
while(p != head){
Node *temp = p->next;
free(p);
p = temp;
}
free(head);
free(l);
}
/*
* 打印链表 时间复杂度O(n)
* @param l: 要操作的链表指针
* **/
void output(List *l) {
if(l == NULL || l->head == NULL) return; // 判断链表是否已经初始化,且链表不能为空
Node *p,*head = l->head; // 定义一个节点指向链表头
p = head->next;
printf("List:[");
while(p != head){
printf("%d ", p->data);
p = p->next;
}
printf("%d]\n", head->data);
}
int main() {
srand(time(0));
#define MAX_OP 20
List *l = init();
for(int i = 0; i < MAX_OP; i++) {
int op = rand() % 4;
int val = rand() % 100;
int ind = rand() % (l->length + 1);
if(op) {
int res = insert(l, ind, val);
printf("insert %d at %d to List = %d\n", val, ind, res);
}else {
int res = delete(l, ind);
printf("erase a item at %d from List = %d\n", ind, res);
}
output(l), printf("\n");
}
reverse(l);
output(l);
clear(l);
#undef MAX_OP
return 0;
}
双链表的基本操作
双向链表也叫双链表。单链表里的指针域只记录了结点的下一个结点,也就是后继结点,而双向链表的指针域还记录了结点的上一个结点,也就是前驱结点。有了这样的结构,我们可以从头结点遍历到尾结点,也可以从尾结点遍历到头结点了。
双向链表操作与单项链表相似,唯一需要注意的就是操作时头尾指针均需要变化。
/*************************************************************************
> File Name: 2.linklist_binary.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年03月29日 星期一 09时15分34秒
> 链表的基本操作
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct Node {
int data;
struct Node *next;
struct Node *prov;
} Node;
typedef struct List {
Node *head; // 头指针地址
int length; // 链表长度
} List;
/*
* 链表初始化操作
* @return 链表指针
* */
List *init_list() {
List *l = (List *)malloc(sizeof(List));
l->head = NULL;
l->length = 0;
return l;
}
/*
* 插入操作,时间复杂度O(n)
* @param l 待操作链表
* @param ind 待插入坐标
* @param val 待插入元素值
* */
int insert(List *l, int ind, int val) {
if(l == NULL) return 0;
if(ind < 0 || ind > l->length) return 0;
Node *node = (Node *)malloc(sizeof(Node));
node->data = val;
node->next = NULL;
node->prov = NULL; // 初始化prov
if(ind == 0) {
node->next = l->head;
if(l->head) l->head->prov = node; // 判断头结点是否为空,否则更新头结点的prov
l->head = node;
}else {
Node *p = l->head;
while(--ind) p = p->next;
node->prov = p;
node->next = p->next;
if(p->next) p->next->prov = node; // 判断下标后的点是否为空,否则更新后面点的prov值
p->next = node;
}
l->length++;
return 1;
}
/*
* 删除操作 时间复杂度O(n)
* @param l待操作链表
* @param ind 待删除元素的坐标
* */
int delete(List *l, int ind) {
if(l == NULL) return 0;
if(ind < 0 || ind >= l->length) return 0;
Node *delete_node;
if(ind == 0) {
delete_node = l->head;
l->head = l->head->next;
if(l->head) l->head->prov = NULL; // 判断头结点是否为空,否则更新头结点的prov
}else {
Node *p = l->head;
while(--ind) p = p->next;
delete_node = p->next;
p->next = delete_node->next;
if(delete_node->next) delete_node->next->prov = p; // 判断删除节点后面是否有点,有的话更改其prov
}
free(delete_node);
l->length--;
return 1;
}
/**
* 反向打印 时间复杂度 O(2n)
* @param l待操作链表
* */
void reverse(List *l) {
if(l == NULL) return;
Node *p = l->head;
while(p && p->next) p = p->next; // 将指针移至最后一个节点
printf("reList:[");
while(p) {
printf("%d ", p->data);
p = p->prov;
}
printf("]\n");
}
/*
* 清理链表 时间复杂度 O(n)
* @param l待操作链表
* **/
void clear(List *l) {
if(l == NULL) return;
Node *p = l->head;
while(p) {
Node *temp = p->next;
free(p);
p = temp;
}
free(l);
}
/*
* 查询链表 时间复杂度 O(n)
* @param l待操作链表
* @param val 待查找的值
* */
int search(List *l, int val) {
if(l == NULL) return -1;
Node *p = l->head;
int i = 0;
while(p) {
if(p->data == val) return i;
p = p->next;
i++;
}
return -1;
}
/*
* 遍历链表 时间复杂度 O(n)
* @param l待操作链表
* **/
void output(List *l) {
if(l == NULL) return;
Node *p = l->head;
printf("List:[");
while(p) {
printf("%d ", p->data);
p = p->next;
}
printf("]\n");
}
int main() {
srand(time(0));
#define MAX_OP 20
List *l = init_list();
for(int i = 0; i < MAX_OP; i++) {
int op = rand() % 4;
int val = rand() % 100;
int ind = rand() % (l->length + 1);
if(op) {
int res = insert(l, ind, val);
printf("insert %d at %d to List = %d\n", val, ind, res);
}else {
int res = delete(l, ind);
printf("erase a item at %d from List = %d\n", ind, res);
}
output(l), printf("\n");
}
reverse(l);
output(l);
clear(l);
#undef MAX_OP
return 0;
}