详细介绍C语言中双向不循环链表的实现和基本操作
目录
1、双向不循环链表的定义和结构
2、判断链表是否为空
3、创建双向不循环链表
4、在双向不循环链表中插入节点
5、从双向不循环链表中删除节点
6、查找链表的元素或元素的下标
7、修改链表的元素
8、获取链表的长度
9、遍历双向不循环链表
10、释放双向不循环链表的内存
11、完整代码
1、双向不循环链表的定义和结构:
我们将解释双向不循环链表的概念,并说明每个节点的结构。每个节点包含一个数据元素,一个指向前一个节点的指针(prev),以及一个指向下一个节点的指针(next)。
// 链表节点结构体
typedef struct Node {
typData data;// 节点数据
int tail;//记录节点数
struct Node *prev;//指向上一个节点的指针
struct Node *next;//指向下一个节点的指针
} Linklist, *Plinklist;
2、判断链表是否为空
// 判断链表是否为空
int isEmpty(Plinklist list){
if(list == NULL || list->tail == 0){
//puts("表是空的!");
return 1;//真空
}else{
return 0;//非空
}
}
3、创建双向不循环链表:
我们将介绍如何创建一个空的双向不循环链表,并确保头节点的正确设置。
初始化(创建):表头不存放数据
// 创建链表
Plinklist createLinkedList(Plinklist *list){
(*list) = (Plinklist)malloc(sizeof(Linklist));//为表头申请空间
if(list == NULL){
perror("createLinkedList list malloc");
}
(*list)->tail = 0;//记录节点数
(*list)->prev = NULL;//头节点的上一个节点置空
(*list)->next = NULL;//头节点的下一个节点置空
return *list;
}
4、在双向不循环链表中插入节点:
我们将讨论在链表的头部、尾部和任意插入节点的方法。这包括更新指针和调整节点的链接。
头插法:
// 在链表头部插入元素
void insertAtHead(Plinklist list, typData value){
if(list == NULL){
puts("insert head arg err");
return ;
}
Plinklist newNode = (Plinklist)malloc(sizeof(Linklist));
if(newNode == NULL){
perror("insert head newNode malloc");//头插入内存分配失败
return ;
}
newNode->data = value;//设置新节点的数据值
newNode->prev = list;//新节点的prev指针指向头节点
newNode->next = list->next;//新节点的next指针的作用是替代头节点的next指针指向NULL
if(list->next != NULL){
list->next->prev = newNode;更新头节点的前驱节点为新节点
}
list->next = newNode;//更新头节点的后继节点为新节点
list->tail++;//链表长度加一
}
尾插法:
// 在链表尾部插入元素
void insertAtTail(Plinklist list, typData value){
if(list == NULL){
puts("insert tail arg err");
return ;
}
Plinklist newNode = (Plinklist)malloc(sizeof(Linklist));
if(newNode == NULL){
perror("insert tail newNode malloc");
return ;
}
newNode->data = value;// 设置新节点的数据值
newNode->next = NULL;// 新节点的后继节点指向NULL
Plinklist current_tail = list;// 重新定义一个新指针指向头节点
while(current_tail->next != NULL){
current_tail = current_tail->next;// 遍历找到链表的尾节点
}
newNode->prev = current_tail;// 新节点的前驱节点指向尾节点
current_tail->next = newNode;// 尾节点的后继节点指向新节点
list->tail++;// 节点数加一
}
指定下标插入法
// 在指定位置插入元素
void insertAtPosition(Plinklist list, int position, typData value){
if(list ==NULL){
puts("insert position arg err");
return ;
}
if(position < 0 || position > (list->tail)){// 检查输入的position下标是否存在
puts("无效位置!");
return ;
}
Plinklist newNode = (Plinklist)malloc(sizeof(Linklist));//创建一个要插入的新节点
newNode->data = value;//设置新节点的数据值
Plinklist current = list;//重新定义一个新指针指向头节点
int count = 0;//定义一个计数变量
while(current != NULL && count < position){
current = current->next;// 找到要插入位置的前一个节点
count++;
}
newNode->next = current->next;// 新节点的后继指向要原删除节点的前一节点的下一个节点
newNode->prev = current;// 新节点的前驱指向新节点的前一节点
if (current->next != NULL) {
current->next->prev = newNode;// 更新原来节点和新节点的连接
}
current->next = newNode;// 将新界嗲连接到原节点之后
list->tail++;
}
5、从双向不循环链表中删除节点:
我们将介绍如何删除链表中的头节点、尾节点和任意节点。这涉及到更新指针和释放节点的内存。
删除头节点:
// 删除头节点
void deleteHead(Plinklist list){
if(list ==NULL){
puts("dalete head arg err");
return ;
}
if(isEmpty(list)){
puts("表是空的!");
return;
}
Plinklist deleteHead = list->next;// 定义一个新的删除指针指向头节点的下一个节点
list->next = deleteHead->next;// 头节点的后继更新为要删除节点的后一个后一个节点也可以是list>next = list->next->next)
if (deleteHead->next != NULL) {
deleteHead->next->prev = list;// 更新头节点和新节点的连接
}
free(deleteHead);//释放删除节点的内存
list->tail--;// 节点数减一
}
删除尾节点:
// 删除尾节点
void deleteTail(Plinklist list){
if(list ==NULL){
puts("dalete tail arg err");
return ;
}
if(isEmpty(list)){
puts("表是空的!");
return;
}
Plinklist current = list;
while(current->next != NULL && current->next->next != NULL){
current = current->next; 找到尾节点
}
Plinklist deleteTail = current->next;
current->next = NULL;// 要删除节点的上一个节点的后继直接连接NULL,断开要删除节点和NULL的连接
if (deleteTail != NULL) {// 判断要删除节点是否为空
free(deleteTail);//不为空则释放该删除节点的内存
list->tail--;// 节点数减一
}
}
删除任意下标的节点:
// 删除指定位置的节点
void deleteAtPosition(Plinklist list, int position){
if (isEmpty(list)) {
printf("链表为空,删不了,根本删不了\n");
return;
}
Plinklist current = list; // 从头节点开始遍历链表,找到要删除节点的前一个节点
int count = 0;
while (current->next != NULL && count < position) {
current = current->next;// 不断更新current指针
count++;
}
if (current->next == NULL) {// 检查删除位置的有效性
printf("无效的删除位置\n");
return;
}
Plinklist deletePosition = current->next;//定义新的指针记录要删除节点的上一个节点
current->next = deletePosition->next;// 要删除节点的上一个节点的后继连接到要删除节点的后一个节点,断开要删除节点的和要删除节点
的上一个节点的后继
if (deletePosition->next != NULL) {
deletePosition->next->prev = current;//更新前后节点的连接
}
free(deletePosition);//释放删除节点的内存
list->tail--;//节点数减一
if (isEmpty(list)) {
printf("链表为空,删不了了,根本删不了了\n");
return;
}
}
6、查找链表的元素或元素的下标
我们将介绍和实现按元素下标查找改下标的元素值和按元素的值查找该元素的下标。
按元素的值查找该元素的下标
// 按值查找节点的位置
int findPositionByValue(Plinklist list, typData value){
if(list ==NULL){
puts("findValue Position arg err");
return -1;
}
if(isEmpty(list)){
puts("表是空的!");
return -1;
}
Plinklist current = list->next;
int position = 0;//定义下标从0开始
while (current != NULL) {
if (current->data == value) {
return position; // 找到节点的数据值等于目标值,返回节点的下标
}
current = current->next;
position++;
}
printf("没有该值!");
return -1; // 未找到目标值,返回-1
}
按元素下标查找该下标的元素值
// 按下标查找节点的值
int findValueByPosition(Plinklist list, int position){
if(list ==NULL){
puts("findPosition Value arg err");
return -1;
}
if(isEmpty(list)){
puts("表是空的!");
return -1;
}
Plinklist current = list->next; // 从第一个节点开始遍历链表
int count = 0;
while (current != NULL && count < position) {
current = current->next;
count++;
}
if (current == NULL) {// 检查下标的有效性
printf("无效的下标\n");
return -1;
}
return current->data; // 返回找到的节点的数据值
}
7、修改链表的元素
我们将实现按原值修改新值和按下标修改值
按下标修改值
// 修改指定位置的节点值
void modifyByPosition(Plinklist list, int position, typData value){
if(list ==NULL){
puts("modifyPosition Value arg err");
return ;
}
if(isEmpty(list)){
puts("表是空的!");
return ;
}
if (isEmpty(list)) {
printf("链表为空,无法修改元素\n");
return;
}
Plinklist current = list->next; // 从第一个节点开始遍历链表
int count = 0;
while (current != NULL && count < position) {
current = current->next;
count++;
}
if (current == NULL) {
printf("无效的下标\n");
return;
}
current->data = value; // 修改节点的数据值
}
按原值修改新值
// 修改指定值的节点值
void modifyValue(Plinklist list, typData oldValue, typData newValue){
if(list ==NULL){
puts("modifyValue Value arg err");
return ;
}
if (isEmpty(list)) {
printf("链表为空,无法修改节点值\n");
return;
}
Plinklist current = list->next; // 从第一个节点开始遍历链表
while (current != NULL) {
if (current->data == oldValue) {
current->data = newValue; // 找到节点的数据值等于旧值,更新节点的数据值为新值
return;
}
current = current->next;
}
printf("未找到目标值\n");
}
8、获取链表的长度
// 获取链表长度
int getLength(Plinklist list){
if(isEmpty(list)){
return -1;
}
return list->tail;//直接返回链表的tail节点数
}
9、遍历双向不循环链表:
我们将展示如何遍历链表并访问每个节点的数据元素。我们将演示前向和后向遍历的方法。
向后遍历:从尾节点遍历到头节点
//向后遍历链表并打印节点值(从尾节点遍历到头节点)
void prev_traverse(Plinklist list){
if(list == NULL){
puts("prev_traverse arg err");
return ;
}
if(isEmpty(list)){
return ;
}
Plinklist current_tail = list;
while(current_tail->next != NULL){
current_tail = current_tail->next;// 找到尾节点
}
Plinklist current = current_tail;
while(current != list){
printf("%d ",current->data);// 打印节点值
current = current->prev;
}
printf("\n");
}
向前遍历:从头节点遍历到尾节点
//向前遍历链表并打印节点值(从头节点遍历到尾节点)
void next_traverse(Plinklist list){
if(list == NULL){
puts("next_traverse arg err");
return ;
}
if(isEmpty(list)){
return ;
}
Plinklist current = list->next;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
10、释放双向不循环链表的内存:
我们将讨论如何正确释放整个链表所占用的内存。
我们调用前面的删除头节点的方法。不断删除头节点直到链表为空
// 清空链表
void clearList(Plinklist list){
while(list->next != NULL){
deleteHead(list);//调用删除头节点函数,实现不断的删除头节点
}
printf("表已被清空!\n");
}
完整代码
分三个文件进行,①接口文件blinklist.h、②blinklist.c、③blinklist_main.c
①blinklist.h
#ifndef _BLINKLIST_H_
#define _BLINKLIST_H_
#include <stdio.h>
#include <stdlib.h>
typedef int typData;
// 链表节点结构体
typedef struct Node {
typData data;// 节点数据
int tail;//记录节点数
struct Node *prev;//指向下一个节点的指针
struct Node *next;//指向下一个节点的指针
} Linklist, *Plinklist;
// 创建链表
Plinklist createLinkedList(Plinklist *list);
// 判断链表是否为空
int isEmpty(Plinklist list);
// 在链表头部插入元素
void insertAtHead(Plinklist list, typData value);
// 在链表尾部插入元素
void insertAtTail(Plinklist list, typData value);
// 在指定位置插入元素
void insertAtPosition(Plinklist list, int position, typData value);
// 删除头节点
void deleteHead(Plinklist list);
// 删除尾节点
void deleteTail(Plinklist list);
// 删除指定位置的节点
void deleteAtPosition(Plinklist list, int position);
// 按值查找节点的位置
int findPositionByValue(Plinklist list, typData value);
// 按下标查找节点的值
int findValueByPosition(Plinklist list, int position);
// 修改指定位置的节点值
void modifyByPosition(Plinklist list, int position, typData value);
// 修改指定值的节点值
void modifyValue(Plinklist list, typData oidValue, typData newValue);
// 获取链表长度
int getLength(Plinklist list);
// 向前遍历链表并打印节点值
void prev_traverse(Plinklist list);
// 向后遍历链表并打印节点值
void next_traverse(Plinklist list);
// 清空链表
void clearList(Plinklist list);
#endif
②blinklist.c
#include "blinklist.h"
/*
// 链表节点结构体
typedef struct Node {
typData data;// 节点数据
int tail;//记录节点数
struct Node *prev;//指向上一个节点的指针
struct Node *next;//指向下一个节点的指针
} Linklist, *Plinklist;
*/
// 创建链表
Plinklist createLinkedList(Plinklist *list){
(*list) = (Plinklist)malloc(sizeof(Linklist));//为表头申请空间
if(list == NULL){
perror("createLinkedList list malloc");
}
(*list)->tail = 0;//记录节点数
(*list)->prev = NULL;//头节点的上一个节点置空
(*list)->next = NULL;//头节点的下一个节点置空
return *list;
}
// 判断链表是否为空
int isEmpty(Plinklist list){
if(list == NULL || list->tail == 0){
//puts("表是空的!");
return 1;//真空
}else{
return 0;//非空
}
}
// 在链表头部插入元素
void insertAtHead(Plinklist list, typData value){
if(list == NULL){
puts("insert head arg err");
return ;
}
Plinklist newNode = (Plinklist)malloc(sizeof(Linklist));
if(newNode == NULL){
perror("insert head newNode malloc");//头插入内存分配失败
return ;
}
newNode->data = value;//设置新节点的数据值
newNode->prev = list;//新节点的prev指针指向头节点
newNode->next = list->next;//新节点的next指针的作用是替代头节点的next指针指向NULL
if(list->next != NULL){
list->next->prev = newNode;更新头节点的前驱节点为新节点
}
list->next = newNode;//更新头节点的后继节点为新节点
list->tail++;//链表长度加一
}
// 在链表尾部插入元素
void insertAtTail(Plinklist list, typData value){
if(list == NULL){
puts("insert tail arg err");
return ;
}
Plinklist newNode = (Plinklist)malloc(sizeof(Linklist));
if(newNode == NULL){
perror("insert tail newNode malloc");
return ;
}
newNode->data = value;// 设置新节点的数据值
newNode->next = NULL;// 新节点的后继节点指向NULL
Plinklist current_tail = list;// 重新定义一个新指针指向头节点
while(current_tail->next != NULL){
current_tail = current_tail->next;// 遍历找到链表的尾节点
}
newNode->prev = current_tail;// 新节点的前驱节点指向尾节点
current_tail->next = newNode;// 尾节点的后继节点指向新节点
list->tail++;// 节点数加一
}
// 在指定位置插入元素
void insertAtPosition(Plinklist list, int position, typData value){
if(list ==NULL){
puts("insert position arg err");
return ;
}
if(position < 0 || position > (list->tail)){// 检查输入的position下标是否存在
puts("无效位置!");
return ;
}
Plinklist newNode = (Plinklist)malloc(sizeof(Linklist));//创建一个要插入的新节点
newNode->data = value;//设置新节点的数据值
Plinklist current = list;//重新定义一个新指针指向头节点
int count = 0;//定义一个计数变量
while(current != NULL && count < position){
current = current->next;// 找到要插入位置的前一个节点
count++;
}
newNode->next = current->next;// 新节点的后继指向要原删除节点的前一节点的下一个节点
newNode->prev = current;// 新节点的前驱指向新节点的前一节点
if (current->next != NULL) {
current->next->prev = newNode;// 更新原来节点和新节点的连接
}
current->next = newNode;// 将新界嗲连接到原节点之后
list->tail++;
}
// 删除头节点
void deleteHead(Plinklist list){
if(list ==NULL){
puts("dalete head arg err");
return ;
}
if(isEmpty(list)){
puts("表是空的!");
return;
}
Plinklist deleteHead = list->next;// 定义一个新的删除指针指向头节点的下一个节点
list->next = deleteHead->next;// 头节点的后继更新为要删除节点的后一个后一个节点(也可以是list>next = list->next->next)
if (deleteHead->next != NULL) {
deleteHead->next->prev = list;// 更新头节点和新节点的连接
}
free(deleteHead);//释放删除节点的内存
list->tail--;// 节点数减一
}
// 删除尾节点
void deleteTail(Plinklist list){
if(list ==NULL){
puts("dalete tail arg err");
return ;
}
if(isEmpty(list)){
puts("表是空的!");
return;
}
Plinklist current = list;
while(current->next != NULL && current->next->next != NULL){
current = current->next; 找到尾节点
}
Plinklist deleteTail = current->next;
current->next = NULL;// 要删除节点的上一个节点的后继直接连接NULL,断开要删除节点和NULL的连接
if (deleteTail != NULL) {// 判断要删除节点是否为空
free(deleteTail);//不为空则释放该删除节点的内存
list->tail--;// 节点数减一
}
}
// 删除指定位置的节点
void deleteAtPosition(Plinklist list, int position){
if (isEmpty(list)) {
printf("链表为空,删不了,根本删不了\n");
return;
}
Plinklist current = list; // 从头节点开始遍历链表,找到要删除节点的前一个节点
int count = 0;
while (current->next != NULL && count < position) {
current = current->next;// 不断更新current指针
count++;
}
if (current->next == NULL) {// 检查删除位置的有效性
printf("无效的删除位置\n");
return;
}
Plinklist deletePosition = current->next;//定义新的指针记录要删除节点的上一个节点
current->next = deletePosition->next;// 要删除节点的上一个节点的后继连接到要删除节点的后一个节点,断开要删除节点的和要删除节点的上一个节点的后继
if (deletePosition->next != NULL) {
deletePosition->next->prev = current;//更新前后节点的连接
}
free(deletePosition);//释放删除节点的内存
list->tail--;//节点数减一
if (isEmpty(list)) {
printf("链表为空,删不了了,根本删不了了\n");
return;
}
}
// 按值查找节点的位置
int findPositionByValue(Plinklist list, typData value){
if(list ==NULL){
puts("findValue Position arg err");
return -1;
}
if(isEmpty(list)){
puts("表是空的!");
return -1;
}
Plinklist current = list->next;
int position = 0;//定义下标从0开始
while (current != NULL) {
if (current->data == value) {
return position; // 找到节点的数据值等于目标值,返回节点的下标
}
current = current->next;
position++;
}
printf("没有该值!");
return -1; // 未找到目标值,返回-1
}
// 按下标查找节点的值
int findValueByPosition(Plinklist list, int position){
if(list ==NULL){
puts("findPosition Value arg err");
return -1;
}
if(isEmpty(list)){
puts("表是空的!");
return -1;
}
Plinklist current = list->next; // 从第一个节点开始遍历链表
int count = 0;
while (current != NULL && count < position) {
current = current->next;
count++;
}
if (current == NULL) {// 检查下标的有效性
printf("无效的下标\n");
return -1;
}
return current->data; // 返回找到的节点的数据值
}
// 修改指定位置的节点值
void modifyByPosition(Plinklist list, int position, typData value){
if(list ==NULL){
puts("modifyPosition Value arg err");
return ;
}
if(isEmpty(list)){
puts("表是空的!");
return ;
}
if (isEmpty(list)) {
printf("链表为空,无法修改元素\n");
return;
}
Plinklist current = list->next; // 从第一个节点开始遍历链表
int count = 0;
while (current != NULL && count < position) {
current = current->next;
count++;
}
if (current == NULL) {
printf("无效的下标\n");
return;
}
current->data = value; // 修改节点的数据值
}
// 修改指定值的节点值
void modifyValue(Plinklist list, typData oldValue, typData newValue){
if(list ==NULL){
puts("modifyValue Value arg err");
return ;
}
if (isEmpty(list)) {
printf("链表为空,无法修改节点值\n");
return;
}
Plinklist current = list->next; // 从第一个节点开始遍历链表
while (current != NULL) {
if (current->data == oldValue) {
current->data = newValue; // 找到节点的数据值等于旧值,更新节点的数据值为新值
return;
}
current = current->next;
}
printf("未找到目标值\n");
}
// 获取链表长度
int getLength(Plinklist list){
if(isEmpty(list)){
return -1;
}
return list->tail;
}
//向后遍历链表并打印节点值(从尾节点遍历到头节点)
void prev_traverse(Plinklist list){
if(list == NULL){
puts("prev_traverse arg err");
return ;
}
if(isEmpty(list)){
return ;
}
Plinklist current_tail = list;
while(current_tail->next != NULL){
current_tail = current_tail->next;// 找到尾节点
}
Plinklist current = current_tail;
while(current != list){
printf("%d ",current->data);// 打印节点值
current = current->prev;
}
printf("\n");
}
//向前遍历链表并打印节点值(从头节点遍历到尾节点)
void next_traverse(Plinklist list){
if(list == NULL){
puts("next_traverse arg err");
return ;
}
if(isEmpty(list)){
return ;
}
Plinklist current = list->next;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 清空链表
void clearList(Plinklist list){
while(list->next != NULL){
deleteHead(list);//调用删除头节点函数,实现不断的删除头节点
}
printf("表已被清空!\n");
}
③blinklist_main.c
#include "blinklist.h"
#include "blinklist.c"
int main() {
Plinklist list;
createLinkedList(&list);
int choice, value, position, oldValue, newValue;
while (1) {
printf("\n*****************************链表操作菜单*****************************\n");
printf("1. 在头部插入元素 2. 在尾部插入元素 3. 在任意位置插入元素\n");
printf("4. 删除头节点 5. 删除尾节点 6. 删除任意位置的节点\n");
printf("7. 按值查找元素的位置 8. 按位置查找元素的值 9. 修改指定位置的节点值\n");
printf("10. 修改指定值的节点值 11. 获取链表长度 12. 遍历链表\n");
printf("13. 清空链表 14. 退出程序\n");
printf("*********************************************************************\n");
printf("请输入操作编号:");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入要插入的元素值:");
scanf("%d", &value);
insertAtHead(list, value);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 2:
printf("请输入要插入的元素值:");
scanf("%d", &value);
insertAtTail(list, value);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 3:
printf("请输入要插入的位置:");
scanf("%d", &position);
printf("请输入要插入的元素值:");
scanf("%d", &value);
insertAtPosition(list, position, value);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 4:
deleteHead(list);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 5:
deleteTail(list);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 6:
printf("请输入要删除的位置:");
scanf("%d", &position);
deleteAtPosition(list, position);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 7:
printf("请输入要查找的元素值:");
scanf("%d", &value);
position = findPositionByValue(list, value);
if (position != -1) {
printf("元素值 %d 的位置是:%d\n", value, position);
}
break;
case 8:
printf("请输入要查找的位置:");
scanf("%d", &position);
value = findValueByPosition(list, position);
if (value != -1) {
printf("位置 %d 上的元素值是:%d\n", position, value);
}
break;
case 9:
printf("请输入要修改的位置:");
scanf("%d", &position);
printf("请输入修改后的元素值:");
scanf("%d", &value);
modifyByPosition(list, position, value);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 10:
printf("请输入要修改的元素值:");
scanf("%d", &oldValue);
printf("请输入修改后的元素值:");
scanf("%d", &newValue);
modifyValue(list, oldValue, newValue);
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 11:
printf("链表的长度是:%d\n", getLength(list));
break;
case 12:
printf("向后遍历:");
prev_traverse(list);
printf("向前遍历:");
next_traverse(list);
break;
case 13:
clearList(list);
break;
case 14:
printf("已退出!!\n");
exit(0);
default:
printf("无效的操作编号!\n");
break;
}
}
return 0;
}