数据结构之双链表
双链表涉及的两个结构体:
描述节点信息的结构体:
struct node {
int data; //节点数据
struct node *next; //保存下一个节点的首地址
struct node *prev; //保存上一个节点的首地址
};
描述整个双链表的结构体
struct list {
struct node head; //头节点
struct node tail; //尾结点
};
这里定义的是变量,哪怕是嵌套的变量,头节点尾节点也会自动分配好内存,不需要再去malloc,但如果是指针,则需要进行malloc
vim list.h
//list.h:双链表声明
#ifndef __LIST_H
#define __LIST_H
#include <stdio.h>
#include <stdlib.h>
//声明描述节点信息的结构体
typedef struct node {
int data;//数据
struct node *prev;//保存上一个节点的首地址
struct node *next;//保存下一个节点的首地址
}node_t;
//声明链表的结构体
typedef struct list {
struct node head;//头结点
struct node tail;//尾节点
}list_t;
extern void list_init(list_t *list);//初始化
extern void list_deinit(list_t *list);//释放所有节点
extern int list_empty(list_t *list);//判断链表是否为空
extern int list_size(list_t *list); //获取数据个数
extern void list_add(list_t *list, int data);//顺序插
extern void list_add_first(list_t *list, int data);//前插入
extern void list_add_last(list_t *list, int data);//后插
extern void list_del(list_t *list, int data);//删除指定节点
extern void list_del_first(list_t *list);//只删除第一个有效节点
extern void list_del_last(list_t *list);//只删除最后一个有效节点
extern int list_get(list_t *list, int index);//根据节点编号获取节点数据
#endif
vim list.c
//list.c:双链表定义
#include "list.h"
//定义初始化函数
void list_init(list_t *list) {
//头指向尾
list->head.next = &list->tail;
//尾指向头
list->tail.prev = &list->head;
//初始化其他成员
list->head.data = 0;
list->tail.data = 0;
list->head.prev = NULL;
list->tail.next = NULL;
}
//定义判断链表空的函数
int list_empty(list_t *list) {
return list->head.next == &list->tail;//空返回1否则返回0
}
//定义获取有效数据个数的函数
int list_size(list_t *list) {
int count = 0; //记录有效节点个数
for(node_t *pnode = &list->head; pnode != &list->tail; pnode=pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid != &list->tail) //pmid不是尾节点那说明它指向的就是有效节点
count++; //更新计数
}
return count;
}
//定义创建新节点函数
static node_t *create_node(int data) {
node_t *pnew = (node_t *)malloc(sizeof(node_t));
pnew->data = data;
pnew->prev = NULL;
pnew->next = NULL;
return pnew; //返回新节点首地址
}
//定义插入新点函数,新节点插入到pfirst和pmid中间即可
static void insert_node(node_t *pfirst, node_t *pmid, node_t *pnew) {
pfirst->next = pnew;
pnew->prev = pfirst;
pnew->next = pmid;
pmid->prev = pnew;
}
//定义顺序插函数
void list_add(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.遍历找到要插入的位置,将新节点插入到pfirst和pmid中间
for(node_t *pnode = &list->head; pnode != &list->tail; pnode=pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid->data > pnew->data || pmid == &list->tail) {
insert_node(pfirst, pmid, pnew);
break;
}
}
}
//定义前插函数
void list_add_first(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.造游标
node_t *pfirst = &list->head; //pfirst指向头结点
node_t *pmid = pfirst->next; //pmid指向第一个节点
node_t *plast = pmid->next; //plast指向后一个节点
//3.插入新节点到pfirst和pmid中间
insert_node(pfirst, pmid, pnew);
}
//定义后插函数
void list_add_last(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.造游标
node_t *pfirst = list->tail.prev; //pfirst指向原先的最后一个节点
node_t *pmid = pfirst->next; //pmid指向尾节点
node_t *plast = pmid->next; //NULL
//3.插入新节点到pfirst和pmid中间
insert_node(pfirst, pmid, pnew);
}
//定义删除节点函数,删除pmid指向的节点
static void del_node(node_t *pfirst, node_t *pmid, node_t *plast) {
pfirst->next = plast;
plast->prev = pfirst;
free(pmid); //释放内存
}
//定义删除节点的函数
void list_del(list_t *list, int data) {
//1.遍历找到要删除的节点,让pmid指向要删除的节点,最后连接pfirst和plast即可
for(node_t *pnode = &list->head; pnode != &list->tail; pnode=pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid->data == data && pmid != &list->tail/*不能删除尾节点*/) {
del_node(pfirst, pmid, plast);
}
}
}
//定义只删除第一个节点的函数
void list_del_first(list_t *list) {
//1.判断链表是否为空
if(list_empty(list)) {
printf("链表空了.\n");
return;
}
//2.定义游标
node_t *pfirst = &list->head;
node_t *pmid = pfirst->next; //pmid指向要删除的第一个节点
node_t *plast = pmid->next;
//3.删除pmid指向的第一个节点并且连接pfirst和plast
del_node(pfirst, pmid, plast);
}
//定义只删除最后一个节点的函数
void list_del_last(list_t *list) {
//1.判断链表是否为空
if(list_empty(list)) {
printf("链表空了.\n");
return;
}
//2.定义游标
node_t *plast = &list->tail; //plast指向尾节点
node_t *pmid = plast->prev; //pmid指向要删除的最后一个节点
node_t *pfirst = pmid->prev; //pfirst指向前一个节点
//3.删除pmid指向的第一个节点并且连接pfirst和plast
del_node(pfirst, pmid, plast);
}
//定义根据节点编号获取节点数据
int list_get(list_t *list, int index) {
int count = 0; //记录循环次数
for(node_t *pnode = &list->head; pnode != &list->tail; pnode = pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(index == count && pmid != &list->tail)
return pmid->data;
count++;
}
}
//定义释放所节点的函数
void list_deinit(list_t *list) {
while(list->head.next != &list->tail) {
node_t *pfirst = &list->head;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
del_node(pfirst, pmid, plast);
}
}
注意第57行的 break;如果在这里不用break退出循环,会造成死循环,pnode会永远指向插入的节点,不断插入不会结束,由于安全等级问题会产生难以预测的问题。
vim main.c
//main.c:测试
#include "list.h"
int main(void) {
list_t list; //定义链表
list_init(&list);//初始化
list_add_first(&list, 50);
list_add_first(&list, 20);
//*(int *)0 = 0;
list_add_last(&list, 70);
list_add_last(&list, 100);//20 50 70 100
list_add(&list, 80);
list_add(&list, 30);
list_add(&list, 40);
list_add(&list, 60);
list_add(&list, 90);
list_add(&list, 10);
int size = list_size(&list);//获取节点个数
for(int i = 0; i < size; i++)
printf("%d ", list_get(&list, i));
printf("\n");
list_del_first(&list);
list_del_last(&list);
list_del(&list, 50);
size = list_size(&list);//获取节点个数
for(int i = 0; i < size; i++)
printf("%d ", list_get(&list, i));
printf("\n");
list_deinit(&list);//清除
return 0;
}
vim Makefile
BIN=list
OBJ=main.o list.o
CC=gcc
$(BIN):$(OBJ)
$(CC) -o $(BIN) $(OBJ)
%.o:%.c
$(CC) -c -o $@ $<
clean:
rm $(BIN) $(OBJ)