在操作系统和一些著名的框架设计中,如信号量、事件、消息队列、任务控制块,任务调度及各种算法中,经常使用到链表。链表太常见了,每当看到链表时,总有些发懵。看着很费力,主要还是对链表指来指去,没有个清晰直观的认识。
那么画一下图吧,在遇到分析链表的问题,直接图示一下比较直观,在脑海中形成一种认识。以下从基础开始分析,对链表进行一下理解。
网上很多对双向链表解释的文章都是用这个结构:
它们的连接情况是这样的:
相当的不直观,今天我要从详细的地址出发来解释双向链表的原理。
现定义一个结构体如下:
struct student{
char name;
struct student *next;
struct student *prior;
};
现在有5个人A,B,C,D,E.这五个人构成的链表如下:
虚线部分为地址值,这个是为了描述方便随便写的值,在创建上述的链表时其实不用关心地址值到底是多少,这些地址都会放到某个变量当中,只要对变量进行赋值传递就能实现链表的构建。这里完全是为了分析才这么写的。从图中很容易看出蓝色的箭头组成了一个单链表,红色的箭头又组成了一个单链表(逆向的)。
下面将分析链表的前插与删除的例子,理解了这两个功能,其他的功能都能自己分析出来了。
双链表的前插,下面这是代码和代码分析。
void InsertBefore(student *p,char x)
{
//在带头结点的双链表中,将值为x的新结点插入*p之前,设p≠NULL
student *s=malloc(sizeof(student)); //申请一段内存空间,指针指向首地址0x0600
s->name=x; //定义name为G
s->prior=p->prior; // p->prior表示0x0500,将它赋给s->prior表示s->prior里面的值是0x0500,从而指向0x0500这个地址即q,如紫线
s->next=p; //p是0x0700,将它赋给s->next,s->next中的值为0x0700,也即s->next指向了p,如绿色线
p->prior->next=s; // p->prior 是0x0500,即指针q所指,所以p->prior->next相当于没插入s之前的0x0700,插入s后,将s的首地址即0x0600赋给这个位置,所以此时,由q 到p的蓝线断裂,这个蓝线目标变成了s,如黑线所示,此时q->next值为0x0600,图上没有改过来。
p->prior=s; //同理,p->prior也指向了s,即p->prior中0x0500变成了0x0600(图上没有改过来),红线断裂。变成墨绿色线。至此前插完成。
}
下面再分析删除,删除比较简单,代码如下:
void DeleteNode(student *p)
{
//在带头结点的双链表中,删除结点*p,设*p为非终端结点
p->prior->next=p->next;// 将p->next即0x0700送到q->next中,即0x0500被替换成了0x0700(图中没改过来),如紫线。
p->next->prior=p->prior;// p->prior为0x0300送到了s->prior即原本是0x0500的地方(图中没改过来),如绿线。
free(p);//将p内存释放,同时将之前的四根红蓝线全部断裂,至此完成删除任务。
}
总结一下就是:比如上图的A B C D E,假如此时还没有C,只有ABDE,需要把C插入到B的后面,那么需要怎么做呢?
假如C的地址是p,B的地址是q,那么肯定是C的前驱和后继需要变,C前面的B的后继需要变,C后面的D的前驱需要变。
分别列出来这些需要变的元素:
如下:
p->next = //C的后继
p->prio = //C的前驱
q->next = //C前面的B的后继
q->next->prio= //C后面的D的前驱
下面赋值就可以了,C的后继是谁?C的前驱又是谁?前驱肯定是B了,因为C的前面就是B,C的后继是B的后继,因为原来B通过自己的后继访问到了D,现在把C插在D前面,
所以把原来B的后继给现在的C,就完成了C与D的链接。
因此,
p->next = q->next;
p->prio = q;
下面剩C前面的B的后继是谁?C后面的D的前驱是谁?
q->next = p; //B后面插入了C,因此B的后继自然是C的地址了。
q->next->prio = p; //C插入到了D的前面,那么D的前驱是谁?自然是( q->next)->prio,这里把q->next括号括起来,便于观察,这个q->next相当于原来D的地址
以下示例:双向循环链表,可以用GDB调试,观察一下:
makefile文件:
########################################
#makefile
########################################
BINARY= a
CC= gcc
LD= ld
CFLAGS= -std=c99 -Wall -g
LDSCRIPT=
LDFLAGS=
OBJS= test.o
#CFLAGS=-std=c99
.PHONY: clean
all:images
images: $(BINARY).exe
$(OBJS):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
%.exe: $(OBJS)
$(CC) -o $(*).exe $(OBJS) $(LDFLAGS) $(LDSCRIPT)
clean:
rm -f *.o
#include <stdio.h>
#include <stdlib.h>
/* Types */
typedef char S8;
typedef unsigned char U8;
typedef short S16;
typedef unsigned short U16;
typedef int S32;
typedef unsigned int U32;
typedef long long S64;
typedef unsigned long long U64;
typedef unsigned char BIT;
typedef unsigned int BOOL;
#define OK 0
#define ERROR 1
#define OVERFLOW 2
#define TRUE 1
#define FALSE 0
typedef struct _DuLNode
{
char data;
struct _DuLNode *next;
struct _DuLNode *prio;
}DuLNode,*DuLinkList;
void print_inf(DuLinkList node)
{
printf("addr:%d\n"
"data:%c\n"
"prio:%d\n"
"next:%d\n",node,node->data,node->prio,node->next);
printf("---------------------------------------------------\r\n");
}
void initList(DuLinkList *L)
{
(*L) = (DuLinkList)malloc(sizeof(DuLNode));
(*L)->data='A';//head
if(*L)
(*L)->next = (*L)->prio = *L;
else
exit(OVERFLOW);
}
int ListLength(DuLinkList L)
{
/* 初始条件:L已存在。操作结果: */
int i=0;
DuLinkList p = L->next; /* p指向第一个结点 */
while(p != L) /* p没到表头 */
{
i++;
p=p->next;
}
return i;
}
DuLinkList GetElemP(DuLinkList L,int i)
{
/* 在双向链表L中返回第i个元素的地址。i为0,返回头结点的地址。若第i个元素不存在,*/
/* 返回NULL */
int j;
DuLinkList p=L; /* p指向头结点 */
if( i<0 || i > ListLength(L)) /* i值不合法 */
return NULL;
for(j=1; j <= i; j++)
p = p->next;
return p;
}
int ListInsert(DuLinkList L,int i,char e)
{
/* 在带头结点的双链循环线性表L中第i个位置之前插入元素e,i的合法值为1≤i≤表长+1 */
/* 改进算法,否则无法在第表长+1个结点之前插入元素 */
DuLinkList p,s;
if(i<1||i>ListLength(L)+1) /* i值不合法 */
return ERROR;
p=GetElemP(L,i-1); /* 在L中确定第i个元素前驱的位置指针p */
if(!p) /* p=NULL,即第i个元素的前驱不存在(设头结点为第1个元素的前驱) */
return ERROR;
s=(DuLinkList)malloc(sizeof(DuLNode));
if(!s)
return OVERFLOW;
s->data=e;
s->prio=p; /* 在第i-1个元素之后插入 */
s->next=p->next;
p->next->prio=s;
p->next=s;
// s->prio=p->prio; /* 在第i-1个元素之前插入 */
// s->next=p;
// p->prio=s;
// p->prio->next=s;
print_inf(L);
print_inf(s);
return OK;
}
//在尾部添加
int ListInsertTail(DuLinkList L,char e)
{
/* 在带头结点的双链循环线性表L中第i个位置之前插入元素e,i的合法值为1≤i≤表长+1 */
/* 改进算法2.18,否则无法在第表长+1个结点之前插入元素 */
int i=0;
DuLinkList p,s;
i = ListLength(L)+1;
p=GetElemP(L,i-1); /* 在L中确定第i个元素前驱的位置指针p */
if(!p) /* p=NULL,即第i个元素的前驱不存在(设头结点为第1个元素的前驱) */
return ERROR;
s=(DuLinkList)malloc(sizeof(DuLNode));
if(!s)
return OVERFLOW;
s->data=e;
s->prio=p; /* 在第i-1个元素之后插入 */
s->next=p->next;
p->next->prio=s;
p->next=s;
// s->prio=p->prio; /* 在第i-1个元素之前插入 */
// s->next=p;
// p->prio=s;
// p->prio->next=s;
print_inf(L);
print_inf(s);
return OK;
}
int ListDelete(DuLinkList L,int i,char *e)
{
/* 删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长 */
DuLinkList p;
if(i<1) /* i值不合法 */
return ERROR;
p=GetElemP(L,i); /* 在L中确定第i个元素的位置指针p */
if(!p) /* p=NULL,即第i个元素不存在 */
return ERROR;
*e=p->data;
p->prio->next=p->next;
p->next->prio=p->prio;
free(p);
return OK;
}
//遍历
int List_ForEach(DuLinkList L)
{
int i=0;
printf("List_ForEach:--------------------------------------\r\n");
print_inf(L);
DuLinkList p=L->next; /* p指向第1个元素 */
while(p != L)
{
print_inf(p);
i++;
p=p->next;
}
return 0;
}
int LocateElem(DuLinkList L,char e,int(*compare)(char,char))
{
/* 初始条件:L已存在,compare()是数据元素判定函数 */
/* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int i=0;
DuLinkList p=L->next; /* p指向第1个元素 */
while(p != L)
{
i++;
if(compare(p->data,e)) /* 找到这样的数据元素*/
return i;
p=p->next;
}
return 0;
}
void DestroyList(DuLinkList *L)
{
DuLinkList q,p=(*L)->next; /* p指向第一个结点 */
while(p != *L) /* p没到表头 */
{
q=p->next;
free(p);
p=q;
}
free(*L);
*L=NULL;
}
void ClearList(DuLinkList L) /* 不改变L */
{
DuLinkList q,p=L->next; /* p指向第一个结点 */
while(p != L) /* p没到表头 */
{
q = p->next;
free(p);
p=q;
}
L->next=L->prio=L; /*头结点的两个指针域均指向自身 */
}
int ListEmpty(DuLinkList L)
{ /* 初始条件:线性表L已存在*/
if(L->next==L&&L->prio==L)
return TRUE;
else
return FALSE;
}
int GetElem(DuLinkList L,int i,char *e)
{
/* 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR */
int j=1; /* j为计数器 */
DuLinkList p = L->next; /* p指向第一个结点 */
while( p!=L && j<i )
{
p=p->next;
j++;
}
if(p==L || j>i) /* 第i个元素不存在 */
return ERROR;
*e=p->data; /* 取第i个元素 */
return OK;
}
int main()
{
printf("----hello test----\r\n");
DuLinkList st=NULL;//Head
initList(&st);
print_inf(st);
ListInsert(st,1,'B');
ListInsert(st,2,'C');
ListInsert(st,2,'D');
ListInsertTail(st,'E');
List_ForEach(st);
char x;
GetElem(st,2,&x);
printf("GetElem is:%c\r\n",x);
system("pause");
return 0;
}