双链表对比单链表只有结构体、初始化、插入和删除有不同(而且每一项只多了几行),其余的完全可以通过单链表的方法来完成。如:输出表、表长、查找元素等。
数据结构-单链表(C语言)_Naughty Chen的博客-CSDN博客
1.函数的声明和自定义
dnode.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct DNode {
ElemType data;
struct DNode* next;
struct DNode* prior;
}DLinkNode;
#ifndef __DNODE_H__
#define __DNODE_H__
//菜单
void menu();
//整体创建链表头插法
void CreateListF(DLinkNode*& L, ElemType a[], int n);
//整体创建链表尾插法
void CreateListR(DLinkNode*& L, ElemType a[], int n);
//初始化
void InitList(DLinkNode*& L);
//销毁
void DestroyList(DLinkNode*& L);
//判断是否为空
bool ListEmpty(DLinkNode* L);
//求链表长度
int ListLength(DLinkNode* L);
//输出链表
void DispList(DLinkNode* L);
//索引查找
bool GetElem(DLinkNode* L, ElemType& e, int i);
//元素查找
int LocateElem(DLinkNode* L, ElemType e);
//插入
bool ListInsert(DLinkNode*& L, ElemType e, int i);
//删除
bool ListDelete(DLinkNode*& L, ElemType& e, int i);
//元素逆置
void reverse(DLinkNode*& L);
#endif // !__LINKNODE_H__
2.双链表的各函数操作
双链表.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "dnode.h"
//菜单
void menu() {
printf("*********************************************************\n");
printf("*********1.插入 2.删除************\n");
printf("*********3.索引搜索 4.元素搜索********\n");
printf("*********5.输出表 6.表长************\n");
printf("*********0.退出 7.逆置 ************\n");
printf("*********************************************************\n");
}
//整体创建链表头插法
void CreateListF(DLinkNode*& L, ElemType a[], int n) {
DLinkNode* s;
L = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建头结点
L->next = NULL;
for (int i = 0; i < n; i++) {
s = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建表中节点
s->data = a[i];
s->next = L->next; //这一句就说明了是头插法
/*双链表就多了以下一个判断和赋值新建节点的前驱指针*/
if (L->next != NULL) {
L->next->prior = s;
}
s->prior = L;
L->next = s; //头结点一直指向首节点
}
}
//整体创建链表尾插法
void CreateListR(DLinkNode*& L, ElemType a[], int n) {
DLinkNode* s;
DLinkNode* r; //创建指针r一直指向尾节点(尾插法)
L = (DLinkNode*)malloc(sizeof(DLinkNode));
L->next = NULL;
r = L; //还未有元素时,尾节点就是头结点
for (int i = 0; i < n; i++) {
s = (DLinkNode*)malloc(sizeof(DLinkNode)); //创建表中节点
s->data = a[i];
r->next = s;
s->prior = r; //双链表就多了行这个
r = s; //指针r一直往后移指向新进来的尾节点
}
r->next = NULL; //最后一个节点的next指针赋空
}
//初始化
void InitList(DLinkNode*& L) {
L = (DLinkNode*)malloc(sizeof(DLinkNode)); //开辟头结点空间
L->next = NULL;
L->prior = NULL;
}
//销毁
void DestroyList(DLinkNode*& L) {
DLinkNode* pre = L; //从头结点逐一释放即可
DLinkNode* p = L->next;
while (p != NULL) {
free(pre);
pre = p;
p = pre->next;
}
free(pre);
}
//判断是否为空
bool ListEmpty(DLinkNode* L) {
return(L->next == NULL);
}
//求链表长度
int ListLength(DLinkNode* L) {
int count = 0;
DLinkNode* p = L;
while (p->next != NULL) { //为什么这里判断条件是p->!=NULL,销毁则是p!=NULL,因为这里指针p是从L开始,头结点是不算表的长度的,如要判断条件也为p!=NULL,那么指针p开始赋值为L->next即可
count++;
p = p->next;
}
return(count);
}
//输出链表
void DispList(DLinkNode* L) {
DLinkNode* p = L->next; //指向首节点
printf("链表中的元素有:");
while (p != NULL) { //遍历链表输出元素
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
//索引查找
bool GetElem(DLinkNode* L, ElemType& e, int i) {
DLinkNode* p = L;
int j = 0;
if (i < 1) {
return false;
}
while (j < i && p != NULL) {
j++; //刚好j++到i-1的位置,完成逻辑-1
p = p->next; //此时指针p指向的就是第i个位置
}
if (p == NULL) { //判断第i个位置是否有元素
return false;
}
else {
e = p->data;
return true;
}
}
//元素查找
int LocateElem(DLinkNode* L, ElemType e) {
DLinkNode* p = L;
int i = 0;
while (p->data != e && p != NULL) { //根据元素遍历链表,找出该元素的位置
p = p->next;
i++;
}
if (p == NULL) { //遍历完都没找到就返回0
return 0;
}
else {
return (i); //否则返回该元素位置
}
}
//插入
bool ListInsert(DLinkNode*& L, ElemType e, int i) {
DLinkNode* p;
DLinkNode* s;
int j = 0;
p = L;
if (i <= 0) {
return false;
}
while (j < i - 1 && p != NULL) { //找到逻辑上第i-1位置的节点
j++;
p = p->next;
}
if (p == NULL) { //判断第i-1位置的节点是否为空,空就错误(因为表是具备索引且按顺序的,没有i-1的节点,就无法插入第i个节点)
return false;
}
else {
s = (DLinkNode*)malloc(sizeof(DLinkNode));
s->data = e;
s->next = p->next; //这是使用尾插法插入,如果是中间插入,则这里的先让s指向p的下一个节点,防止没指针指向下一个空间,避免内存泄漏
/*从这里开始,上面和单链表都是一样的,下面节点的插入才有区别*/
if (p->next != NULL) { //判断要插入的i位置是否有元素,有则要先改变i位置节点的前驱指针
p->next->prior = s;
}
s->prior = p; //随后设置s节点的前驱与后继
p->next = s;
return true;
}
}
//删除
bool ListDelete(DLinkNode*& L, ElemType& e, int i) {
int j = 0;
DLinkNode* p = L;
DLinkNode* s;
if (i <= 0) {
return false;
}
while (j < i - 1 && p != NULL) { //同插入一样先找到逻辑上i-1位置上的节点
j++;
p = p->next;
}
if (p == NULL) { //不存在i-1也就不存在i,返回错误
return false;
}
else {
s = p->next; //让指针s指向p->next,也就是s指向第i个元素
if (s == NULL) { //判断是否存在第i个元素,不存在就返回错误
return false;
}
e = s->data; //这行操作可以用来告诉用户删除的元素值为多少
p->next = s->next; //让第i-1位置的节点指向第i+1位置的节点,将第i个节点从表中独立出来
/*从这里开始,上面和单链表都是一样的,双链表的删除只是多了一个if判断s节点是否存在后继节点,修改其前驱指针*/
if (s->next != NULL) {
s->next->prior = p;
}
free(s); //释放掉第i个节点空间
return true; //删除成功
}
}
//逆置
void reverse(DLinkNode*& L) {
DLinkNode* p = L->next;
DLinkNode* q;
L->next = NULL;
while (p != NULL) { //遍历所有元素
q = p->next;
p->next = L->next;
if (L->next != NULL) { ///头插法 一开始:头->1->2 第一轮后:头->1 2 第二轮后:头->2->1 (一个一个拆出来进行头插法重新插入排列)
L->next->prior = p;
}
L->next = p;
p->prior = L;
p = q;
}
}
3.运行
TEST.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "dnode.h"
int main() {
DLinkNode* L;
ElemType e;
int input = 0;
int i = 0;
int n = 0;
InitList(L);
do {
menu();
printf("请选择模式:");
scanf("%d", &input);
switch (input)
{
case 0:
DestroyList(L);
break;
case 1:
printf("请输入要插入的元素:");
scanf("%d", &e);
printf("请输入要插入的位置:");
scanf("%d", &i);
if (ListInsert(L, e, i)) {
printf("插入成功\n");
}
else {
printf("插入失败\n");
}
break;
case 2:
printf("请输入要删除元素的位置:");
scanf("%d", &e);
if (ListDelete(L, e, i)) {
printf("删除成功,删除元素为:%d\n", e);
}
else {
printf("删除失败\n");
}
break;
case 3:
printf("请输入要查找哪个位置的元素:");
scanf("%d", &i);
GetElem(L, e, i);
printf("该位置的元素为:%d\n", e);
break;
case 4:
printf("请输入要查找哪个元素的位置:");
scanf("%d", &e);
n = LocateElem(L, e);
printf("该元素的位置为:%d\n", n);
break;
case 5:
DispList(L);
break;
case 6:
printf("该表表长为:%d\n", ListLength(L));
break;
case 7:
reverse(L);
break;
default:
break;
}
} while (input);
return 0;
}
4.结果
如结果,完成了逆置操作,因为其他功能和单链表一样,这里结果不展示了。可参考: