链表中的元素都是一个包含三个域的对象:一个关键字(或数据)域:data;两个指针(或链)域:next和prev;根据指针域的不同可以把链表细分为:单链表,双链表,循环链表及静态链表。
下面以单链表为研究对象来介绍链表的基本运算。
单链表的C语言描述:
typedef struct node
{
int data; /*数据域*/
struct node *next; /*后继指针域*/
}linklist;
linklist *head,*p;
头插法建链表与尾插法两者之间最大的不同在于输入的结点次序在链表中的排列次序,头插法跟输入
次序相反,尾插法和输入次序相同;所以一般用尾插法建表。
linklist* CreatList() /*头插法建表*/
{
char ch; /*逐个输入字符,以‘$‘为结束符,返回单链表头指针*/
linklist *head,*s;
head = NULL; /*链表开始为空*/
ch = getchar(); /*读入第一个结点的值*/
while(ch != '$')
{
s = (struct node*)malloc(sizeof(linklist)); /*生成新结点*/
s->data = ch; /*将输入数据放入新结点的数据域中*/
s->next = head;
head = s; /*将新结点插入到表头上*/
ch = getchar();
}
return head; /*返回链表头指针*/
}
在讲尾插法建表之前,先引入“头结点”;之所以要引入它,是因为它能使开始结点的处理和
其他操作可以一致处理;而且它也统一了空表与非空表的操作。头结点的数据域不存储信息,但
是在有的应用中,我们可以利用该域来存放表的长度等附加信息。
linklist* CreateList() /*尾插法建表,返回表头指针*/
{
char ch;
linklist *head,*s,*r;
head = (linklist*)malloc(sizeof(linklist)); /*生成头结点*/
r = head; /*尾指针初值指向头结点*/
ch = getchar();
while(ch != '$')
{
s = (linklist*)malloc(sizeof(linklist)); /*生成新结点*s */
s->data = ch;
r->next = s; /*新结点插入表尾*/
r = s; /*尾指针指向新的表尾*/
ch = getchar();
}
r->next = NULL;
return head;
}
接下来,我觉得的链表的遍历在链表的运算中是不可忽视的,虽然简单。
void ShowList(linklist *p)
{
while(p)
{
printf("%c",p->data);
p = p->next;
}
}
再来看下链表的插入运算:这里只讨论前插的两种不同操作;普通前插法的基本思路是先遍历链表
找到其前趋结点,然后再插入;而改进的前插法思路是直接插入然后交换两结点的值;
void InsertBefore(linklist *p,char c) /*将值为c的新结点插入到*p之前,改进的前插法*/
{
linklist *s;
s = (linklist*)malloc(sizeof(linklist));
s->data = p->data; /*交换*s和*p的值*/
s->next = p->next;
p->data = c;
p->next = s;
}
这是一种很有用的思想尤其在处理单链表的排序问题时。
双链表在单链表的每个结点里再增加一个前趋指针域prev;用C语言描述为:
typedef struct dnode
{
int data;
struct dnode *prev,*next;
}dlinklist;
dlinklist *head;
双链表是一种对称结构,它使链表的基本操作变的简单。
头指针head唯一确定的双链表的实现: