C语言链表的理解

在操作系统和一些著名的框架设计中,如信号量、事件、消息队列、任务控制块,任务调度及各种算法中,经常使用到链表。链表太常见了,每当看到链表时,总有些发懵。看着很费力,主要还是对链表指来指去,没有个清晰直观的认识。

那么画一下图吧,在遇到分析链表的问题,直接图示一下比较直观,在脑海中形成一种认识。以下从基础开始分析,对链表进行一下理解。

网上很多对双向链表解释的文章都是用这个结构:

  

它们的连接情况是这样的:

  

相当的不直观,今天我要从详细的地址出发来解释双向链表的原理。

现定义一个结构体如下:

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值