一、双向链表的概念

双向链表基于单链表。单链表是单向的,有一个头结点,一个尾结点,要访问任何结点,都必须知道头结点,不能逆着进行。而双链表添加了一个指针域,通过两个指针域,分别指向结点的前结点和后结点。这样的话,可以通过双链表的任何结点,访问到它的前结点和后结点。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点的地址,一般称为右链域;一个存储直接前驱结点地址,一般称之为左链域。

双向链表结构示意图

表头为空,表头的后继节点为"节点10"(数据为10的节点);"节点10"的后继节点是"节点20"(数据为10的节点),"节点20"的前继节点是"节点10";"节点20"的后继节点是"节点30","节点30"的前继节点是"节点20";...;末尾节点的后继节点是表头。

双链表删除节点

删除"节点30"
删除之前:"节点20"的后继节点为"节点30","节点30" 的前继节点为"节点20"。"节点30"的后继节点为"节点40","节点40" 的前继节点为"节点30"。
删除之后:"节点20"的后继节点为"节点40","节点40" 的前继节点为"节点20"。

双链表添加节点

在"节点10"与"节点20"之间添加"节点15"
添加之前:"节点10"的后继节点为"节点20","节点20" 的前继节点为"节点10"。
添加之后:"节点10"的后继节点为"节点15","节点15" 的前继节点为"节点10"。"节点15"的后继节点为"节点20","节点20" 的前继节点为"节点15"。

二、C语言实现双向链表

2.1 头文件

 #pragma once
//新建双向链表。成功返回链表头,否则,返回NULL
extern int create_dLink();
//撤销双向链表,成功返回0,否则返回-1
extern int destory_dLink();
//双向列表是否为空,为空返回1,否则返回0
extern int is_empty_dLink();
//双向链表的大小
extern int dLink_size();
//获取索引index位置的元素,成功返回节点指针,否则返回NULL
extern void* dLink_get(int index);
//获取双向链表中第一个元素,成功返回节点指针,否则返回NULL
extern void* dLink_getFirst();
//获取双向链表中最后一个元素,成功返回节点指针,否则返回NULL
extern void* dLink_getTail();
/*
链表中增加
*/
//在Index位置插值value,成功返回0,否则返回-1;
extern int dLink_insert(int index,void * pVal);
//在表头插入值
extern int dLink_insert_head(void *pVal);
//在表尾插入值
extern int dLink_insert_tail(void *pVal);
/*
链表中删除
*/
//在index处删除
extern int dLink_delete(int index);
//删除第一个节点
extern int dLink_delete_first();
//闪电湖第二个节点
extern int dLink_delete_tail();
2.2 .c
 #include<stdio.h>
#include "double_link.h"
#include<malloc.h> //双向链表节点
typedef struct My_node
{
struct My_node *prev;
struct My_node *pNext;
void * p;
}my_node;
//b表头不存放元素值
my_node *phead = NULL;
//节点的个数
int node_count = ;
//创建节点,成功返回节点指针,否则,返回NULL
my_node* create_node(void *pVal)
{
my_node *pnode = NULL;
pnode = (my_node*)malloc(sizeof(My_node));
if (!pnode)
{
printf("create pnode error\n");
return NULL;
}
//默认的,pnode的前一节点和后一节点都指向他自己
pnode->prev = pnode->pNext = pnode;
//节点的值为pVal
pnode->p = pVal;
return pnode;
} //新建双向链表 成功返回0 否则返回-1
int create_dLink()
{
phead = create_node(NULL);
if (!phead)
return -;
//设置节点的个数
node_count = ;
return ;
} int destory_dLink()
{
if (!phead)
{
printf("%s failed! dlink is null!\n", __func__);
return -;
}
My_node*pnode = phead->pNext;
my_node* ptmp = NULL;
if (pnode!=phead)
{
ptmp = pnode;
pnode = pnode->pNext;
free(pnode);
}
free(phead);
phead = NULL;
node_count = ;
return ;
} int is_empty_dLink()
{
return node_count==;
} int dLink_size()
{
return node_count;
}
//获取双向链表中第Index位置的节点
my_node* get_node(int index)
{
if (index< || index >= node_count)
{
printf("%s failed ! index out of bound\n", __func__);
return NULL;
}
//正向查找
if (index <= (node_count / ))
{
int i = ;
my_node *pnode = phead->pNext;
while ((i++)<index)
{
pnode = pnode->pNext;
}
return pnode;
}
//反向查找
int j = ;
int rindex = node_count - index - ;
my_node *rnode = phead->prev;
while ((j++)<rindex)
{
rnode = rnode->prev;
}
return rnode;
}
void * dLink_get(int index)
{
my_node *pindex = get_node(index);
if (!pindex)
{
printf("%s failed!\n", __func__);
return NULL;
}
return pindex->p;
} //获取第一个节点
void * dLink_getFirst()
{
return get_node() ;
}
//获取最后一个节点
void * dLink_getTail()
{
return get_node(node_count-);
}
//将值插入到index位置,成功返回0;否则 返回-1
int dLink_insert(int index, void * pVal)
{
//插入表头
if (index == )
return dLink_insert_head(pVal);
//获取要插入位置对应的节点
my_node* pindex = get_node(index);
if (!pindex)
return -;
//创建节点
my_node* pnode = create_node(pVal);
if (!pnode)
return -;
pnode->prev = pindex->prev;
pnode->pNext = pindex;
pindex->prev->pNext = pnode;
pindex->prev = pnode;
node_count++;
return ;
}
//数值插入表头
int dLink_insert_head(void * pVal)
{
my_node* pnode = create_node(pVal);
if (!pnode)
return -;
pnode->prev = phead;
pnode->pNext = phead->pNext; phead->pNext->prev = pnode;
phead->pNext = pnode;
node_count++;
return ;
} int dLink_insert_tail(void * pVal)
{
my_node* pnode = create_node(pVal);
if (!pnode)
return -;
pnode->pNext = phead;
pnode->prev = phead->prev;
phead->prev->pNext = pnode;
phead->prev = pnode;
return ;
} int dLink_delete(int index)
{
my_node* pindex = get_node(index);
if (!pindex)
{
printf("%s failed! the index in out of bound\n",__func__);
return -;
}
pindex->pNext->prev = pindex->prev;
pindex->prev->pNext = pindex->pNext;
free(pindex);
node_count--;
return ;
} int dLink_delete_first()
{
return dLink_delete();
} int dLink_delete_tail()
{
return dLink_delete(node_count-);
}

2.3 test测试代码

 #include<stdio.h>
#include"double_link.h"
//1.双向链表操作数为int
void int_test()
{
int arr[] = {,,,,,,,,,};
printf("xxxxxxxxxxxxxxxxx\n");
create_dLink(); //创建链表
dLink_insert(, &arr[]); //双向链表表头插入
dLink_insert(, &arr[]); //双向链表表头插入
dLink_insert(, &arr[]); //双向链表表头插入
dLink_insert(, &arr[]); //双向链表表头插入
dLink_insert(, &arr[]); //双向链表表头插入
dLink_insert(, &arr[]); //双向链表表头插入
printf("is_empty_dLink()=%d\n",is_empty_dLink()); //双向链表是否为空
printf("dLink_size()=%d\n", dLink_size()); //双向链表的大小
//遍历双向链表
int i ;
int * p ;
int sz = dLink_size();
for ( i = ; i < sz; i++)
{
p = (int*)dLink_get(i);
printf("dLink_get(%d)=%d\n",i,*p);
}
destory_dLink();
} //2.操作数为字符串
void string_test()
{
char* str[] = {"one","two","three","four","five"};
create_dLink(); //创建链表
dLink_insert(, str[]); //双向链表表头插入
dLink_insert(, str[]); //双向链表表头插入
dLink_insert(, str[]); //双向链表表头插入
printf("is_empty_dLink()=%d\n", is_empty_dLink()); //双向链表是否为空
printf("dLink_size()=%d\n", dLink_size()); //双向链表的大小
//遍历双向链表
int i ;
char * p ;
int sz = dLink_size();
for (i = ; i < sz; i++)
{
p = (char*)dLink_get(i);
printf("dLink_get(%d)=%s\n", i, p);
}
destory_dLink();
}
//3.双向链表为结构体
typedef struct MyStruct
{
int id;
char name[];
} stu;
stu arr_stu[] =
{
{,"lii"},
{ ,"mike" },
{ ,"lucky" },
{ ,"eric" },
};
#define arr_stu_size ((sizeof(arr_stu))/(sizeof(arr_stu[0])))
void stuc_test()
{
create_dLink(); //创建链表
dLink_insert(, &arr_stu[]); //双向链表表头插入
dLink_insert(, &arr_stu[]); //双向链表表头插入
dLink_insert(, &arr_stu[]); //双向链表表头插入
printf("is_empty_dLink()=%d\n", is_empty_dLink()); //双向链表是否为空
printf("dLink_size()=%d\n", dLink_size()); //双向链表的大小
//遍历双向链表
int i ;
stu * p ;
int sz = dLink_size();
for (i = ; i < sz; i++)
{
p = (stu*)dLink_get(i);
printf("dLink_get(%d)=[%d,%s]\n", i, p->id,p->name);
}
destory_dLink();
}
int main()
{
int_test();
string_test();
stuc_test(); return ;
}

2.34结果显示

C语言双向链表讲解的更多相关文章

  1. C语言双向链表

    原文:C语言双向链表 今天写了点双向链表的各种操作,写插入的时候费了点时间,不过,现在看来还是值得耗费那点时间去写的,这种小东西应该能信手拈来才行啊. /*双向链表*/ #include <st ...

  2. 第二十四节:Java语言基础-讲解数组的综合应用

    数组的综合应用 // 打印数组 public static void printArray(int[] arr) { for(int x=0;x<arr.length;x++) { if(x!= ...

  3. 传智播客C语言视频第二季(第一季基础上增加诸多C语言案例讲解,有效下载期为10.5-10.10关闭)

    卷 backup 的文件夹 PATH 列表卷序列号为 00000025 D4A8:14B0J:.│  1.txt│  c语言经典案例效果图示.doc│  ├─1传智播客_尹成_C语言从菜鸟到高手_第一 ...

  4. 贪吃蛇游戏——C语言双向链表实现

    采用了双向链表结点来模拟蛇身结点: 通过C语言光标控制函数来打印地图.蛇身和食物: /************************** *************************** 贪吃 ...

  5. Go语言之讲解GOROOT、GOPATH、GOBIN

    Go是一门全新的静态类型开发语言,具有自动垃圾回收,丰富的内置类型,函数多返回值,错误处理,匿名函数,并发编程,反射等特性. go命令依赖一个重要的环境变量:$GOPATH GOPATH允许多个目录, ...

  6. (C/C++学习)18.C语言双向链表

    说明:数组提供了连续内存空间的访问和使用,而链表是对内存零碎空间的有效组织和使用.链表又分为单向链表和双向链表,单向链表仅提供了链表的单方向访问,相比之下,双向链表则显得十分方便. 一.单向链表的节点 ...

  7. c语言递归讲解分析

    C语言允许函数调用它自己,这种调用的过程称为"递归(recursion)" 举例说明,如下代码: #include <stdio.h> void up_and_down ...

  8. C语言递归函数讲解

    递归函数是什么? 是函数.................... 你可以把它理解成是for循环与死循环的结合的函数.简单的说:递归函数是有条件终止的死循环函数: 死循环函数这里是指在函数体中调用自身: ...

  9. C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻

    这是道哥的第014篇原创 目录 一.前言 二.变量与指针的本质 1. 内存地址 2. 32位与64位系统 3. 变量 4. 指针变量 5. 操作指针变量 5.1 指针变量自身的值 5.2 获取指针变量 ...

随机推荐

  1. c++中的类(构造函数,析构函数的执行顺序)

    类对象的初始化顺序 新对象的生成经历初始化阶段(初始化列表显式或者隐式的完成<这部分有点像java里面的初始化块>)——> 构造函数体赋值两个阶段 1,类对象初始化的顺序(对于没有父 ...

  2. File类 ReadAllBytes() ReadAllLines() ReadAllText()

    用 File 类里的 ReadAllLines方法读取文件string[] contents = File.ReadAllLines(@"F:\Users\zhuotu003\Desktop ...

  3. JSON.stringify方法报错:Converting circular structure to JSON

    别以为JSON.parse(JSON.stringify(data))做深拷贝无敌,对于以下这种情况,当你需要保留父级对象,即 对象存在循环引用,就会报错. var a = [ { "id& ...

  4. leetcode-mid-backtracking -46. Permutations-NO

    mycode 没有通过,其实只需要把temp.append改为temp+[nums[i]]即可 def permute(nums): def dfs(res,nums,temp): print(num ...

  5. acl权限设置

    acl权限分配 1.setfacl设置文件或目录的权限 setfacl -m u:user:rw text setfacl -m user:rwx /mnt 2.getfacl查看文件或目录的权限 g ...

  6. Mybaits二级缓存的使用与配置

    什么是延迟加载  resultMap中的association和collection标签具有延迟加载的功能. 延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息.使用关联信息时再去加载关联信 ...

  7. 【洛谷T89353 【BIO】RGB三角形】

    题目链接 这个题我一开始显然直接暴力 然后30分(但是应用数据分治的我通过复杂度判断并且其余输出0的能力硬生生的拿下了60分) 主要还是讲正解 这里有一个结论 这样一个图,红点的值可以通过两个黄点来判 ...

  8. PADS LAYOUT的一般流程

    1.概述    本文档的目的在于说明使用PADS的印制板设计软件PowerPCB进行印制板设计的流程和一些注意事项,为一个工作组的设计人员提供设计规 范,方便设计人员之间进行交流和相互检查. 2.设计 ...

  9. 原生JS去重

    方式一: function deleteRepetionChar(arr){ //先判断输入进来的是数组对象还是字符串 if( typeof arr == "object"){ v ...

  10. Windows操作路由表

    route print route add 172.17.0.0 mask 255.255.0.0 192.168.99.100 route delete 172.17.0.0 mask 255.25 ...