一、单链表
1、结点:为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。
2、单链表:n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
3、头指针:我们把链表中第一个结点的存储位置叫做头指针。(单链表L:L既是单链表的名字,也是其头指针。)
4、规定线性链表的最后一个结点指针为"空"(通常用NULL表示) 。
5、头结点:有时为了更加方便的对链表进行操作,会在单链表的第一个结点前设置一个结点,称为头结点。头结点的数据域可以不存储任何信息,其指针域存储指向第一个结点的指针。
6、空表:线性表为空,则头结点的指针域为空,表示如下
或
我们大概大概知道了用图示表达了内存中单链表的存储状态。但我们关心的是它在内存中的位置吗?不是的,我们关心的是它所表示的线性表中的数据元素及数据元素之间的逻辑关系。所以我们改用更方便的存储示意图表示单链表,如下
若带有头结点的单链表,则如图所示
二、 单链表存储结构
typedef int ElemType;//根据实际情况而定,这里假设为int
/*线性表的单链表的存储结构*/
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;//定义LinkList
三、单链表的基本操作
1、创建链表
int CreateListHead(LinkList *L,int n)//注意L为二级指针
{
LinkList p;
int i;
/*先建立一个带头结点的单链表*/
*L = (LinkList)malloc(sizeof(Node));
if(!(*L))
return false;
(*L)->next = NULL;
srand(time(0));//初始化随机种子
for(i=0;i<n;i++)
{
p = (LinkList)malloc(sizeof(Node));//生成新的结点
p->data = rand()%100+1;//随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p;//插入到表头,即插入头结点后其它结点之前
}
return true;
}
int CreateListTail(LinkList *L,int n)//注意L为二级指针
{
LinkList p,rear;
int i;
/*先建立一个带头结点的单链表*/
*L = (LinkList)malloc(sizeof(Node));
if(!(*L))
return false;
(*L)->next = NULL;
rear = *L;/*rear为指向尾部结点的指针*/
srand(time(0));//初始化随机种子
for(i=0;i<n;i++)
{
p = (LinkList)malloc(sizeof(Node));//生成新的结点
p->data = rand()%100+1;//随机生成100以内的数字
rear->next = p;//将表尾终端结点的指针的后继指针指向新生成的结点
rear = p;//将当前的新结点定义为表尾终端结点
}
rear->next = NULL;//当前链表结束
return true;
}
ps: CreateListXXX(LinkList *L,int n)传入的是二级指针的原因
1.指针变量是变量,只不过是特殊的变量而不是普通变量,指针变量存储的是地址(变量的地址,数组名,函数指针等等)
2.C语言函数形参是传值调用,函数里使用的参数实际是实参的副本而不是实参本身,函数退出后副本会被释放!因此要在函数里改变函数外的变量,要进行传址调用。
3.所以创建链表,要在函数里改变函数外的一级指针变量,需要传入一级指针变量的地址。因此创建链表的函数参数形参LinkList *L应为二级指针,如果为一级指针,那么应传入的是普通变量的地址,显然不符合期待。
2、链表是否为空
int ListEmpty(LinkList L)
{
if(!L)
{
printf("带头结点的链表不存在\n");
return false;
}
if(L->next)//非空链表
return true;
else
return false;
}
3、链表的长度
int ListLenght(LinkList L)
{
if(!L)
return false;
LinkList p;
int i = 0;
p = L->next;
while(p)
{
i++;
p = p->next;
}
return i;
}
4、获取第i个位置的元素
int GetElem(LinkList L,int i,ElemType *e)
{
if(!L)
return false;
LinkList p;
int j = 1;
p = L;
while(p && j<i)
{
j++;
p = p->next;
}
if(!(p->next))
return false;
*e = p->next->data;
return true;
}
5、查找元素,并返回其位置
/*在线性表L中查找与给定的e相等的元素,如果查找成功,返回该元素在表中序号表示成功,否则返回0表示失败*/
int LocateElem(LinkList L,ElemType e)
{
if(!L)
return false;
LinkList p;
p = L;
int i = 0;
while(p)
{
++i;
if(p->next)
if(e == p->next->data)
return i;
p = p->next;
}
return false;
}
6、链表的插入
/*初始条件:顺序线性表L已存在,1<=i<=ListLebgth(L)
操作结果:在L中第i个结点位置之前插入新的数据元素e,L的长度加1
*/
int ListInser(LinkList *L,int i,ElemType e)
{
if(!(*L) || (i<1))
return false;
LinkList p;
int j = 1;
p = *L;
while(p && j<i)//寻找第i-1个结点
{
j++;
p = p->next;
}
if(!p)
return false;
LinkList s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
7、链表的删除
int ListDelete(LinkList *L,int i,ElemType *e)
{
if(!(*L) || (i<1))
return false;
LinkList p,q;
int j = 1;
p = *L;
while(p->next && j<i)//遍历寻找第i-1个结点
{
j++;
p = p->next;
}
if(!(p->next))//第i个结点不存在
return false;
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return true;
}
8、清空链表
/*初始条件:带头结点的单链表L已存在,操作结果:将L重置为空表*/
int ClearList(LinkList *L)
{
if(!(*L))
return false;
LinkList p,q;
p = (*L)->next;//p指向第一个结点
(*L)->next = NULL;//头结点指针域为空
while(p)
{
q = p;
p = p->next;
free(q);
}
return true;
}
9、打印链表
int printfList(LinkList L)
{
if(!L)
return false;
LinkList p;
p=L->next;
if(!p)
{
printf("链表为空\n");
return false;
}
while(p)
{
printf("p->data:%d\n",p->data);
p=p->next;
}
printf("\n");
return true;
}
四、测试代码及测试结果
#include <stdio.h>
#include <malloc.h>
#include <time.h>
#include <Windows.h>
/*声明:链表是带头结点的线性表*/
void main(void)
{
LinkList L;
CreateListTail(&L,10);
ListEmpty(L);
int len = ListLenght(L);
printf("===========len=%d===========\n",len);
printfList(L);
int pos = LocateElem(L,8888);
printf("查找数据8888是否成功:pos=%d\n",pos);
printf("插入数据8888\n");
ListInser(&L,4,8888);
pos = LocateElem(L,8888);
printf("查找数据8888是否成功:pos=%d\n",pos);
printfList(L);
ElemType e;
GetElem(L,4,&e);
printf("线性表的第四个元素值:%d\n",e);
printf("删除数据8888\n");
ListDelete(&L,4,&e);
printfList(L);
printf("清空链表\n");
ClearList(&L);
printfList(L);
system("pause");
}
五、拓展 (应用)
1、单链表的合并
/*带头结点的单链表合并操作
说明:
1.已知La和Lb为升序线性链表
2.要求合并La和Lb为Lc且Lc为升序*/
int LinkedListMergeLaLb(LinkList La, LinkList Lb, LinkList *Lc)
{
LinkList pa,pb,pc;//pc为指向Lc的尾指针
pa=La->next;
pb=Lb->next;
*Lc=La; //借用表La的头结点作为表Lc的头结点
pc=*Lc;
while((pa!=NULL)&&(pb!=NULL))
{
if(pa->data<=pb->data)
{
pc->next=pa;
pc=pa;
pa=pa->next;
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
if(pa!=NULL)
{
while(pb)
{
pc->next=pa;
pc = pa;
pa = pa->next;
}
}
else
{
while(pb)
{
pc->next=pb;
pc = pb;
pb = pb->next;
}
}
free(pb); //将Lb的表头结点释放
return true;
}
2、链表的反转(逆序)
1)就地反转链表
/*************实现方法一:就地反转链表*************/
int reverseList(LinkList *L)
{
LinkList prev,pCur;
prev = (*L)->next;
pCur = prev->next;
while(pCur)
{
prev->next = pCur->next;
pCur->next = (*L)->next;//pCur->next始终指向第一个结点
(*L)->next = pCur;
pCur = prev->next;
}
return true;
}
2)头插法反转链表
/*************实现方法二:头插法反转链表*************/
int reverseList2(LinkList *L)
{
LinkList pNext,pCur;
pCur = (*L)->next;
LinkList p = *L;
p->next = NULL;
while(pCur)
{
pNext = pCur->next;
pCur->next = p->next;
p->next = pCur;
pCur = pNext;
}
return true;
}
void main(void)
{
LinkList L;
CreateListTail(&L,10);
printfList(L);
reverseList(&L);
printfList(L);
reverseList2(&L);
printfList(L);
system("pause");
}