单链表上的基本运算
单链表的存储定义:
typedef struct Node
{
ElemType data;
struct Node* next;
}Node,*Linklist; //Linklist为结构指针类型
1、初始化单链表
InitList(Linklist *L)
{
*L=(Linklist)malloc(sizeof(Node)); //建立头节点
(*L)->next==NULL; //建立空的单链表L
}
注:L是指向头结点的指针(指向指针的指针)。
2、建立单链表
假设线性表中结点数据类型是字符,并以“$”作为输入结束符。
(1)头插法建表:
重复读数据,生成新结点,存数据到新结点数据域;
将新结点插入表头结点之后,直至读入结束标志为止。
void CreateFromHead(LinkList L){ //L是带头结点的空链表指针
{
Node *s,*r;
char c;
int flag=1;
r=L; //r指针动态指向链表的当前表尾
while(flag);
{
c=getchar();
if(c!='$')
{
s=(Node*)malloc(sizeof(Node)); //建立新结点s
s->data=c;
r->next=s;
r=s;
}
else
{
flag=0;
r->next=NULL; //将最后一个结点的next域置为空
}
}
}
(2)尾插法建表
指针r跟踪链尾结点;
新结点追加。
void CreateFromHead(LinkList L){ //L是带头结点的空链表指针
{
Node *s;
char c;
int flag=1;
while(flag);
{
c=getchar();
if(c!='$')
{
s=(Node*)malloc(sizeof(Node)); //建立新结点s
s->data=c;
}
else flag=0;
}
}
3、单链表查找
(1)按序号查找:查找带头结点单链表中第i个结点。
算法思想:
用一指针(p)从头结点(L)开始顺着链扫描结点,同时计数;
直到扫描了i个结点或链表扫描完。
Node* Get(LinkList L,int i) /*找到(1≤i≤n),返回该结点; 否则返回NULL*/
{
int j;
Node *p;
if(i<=0) return NULL;
p=L; j=0; /*从头结点(编号0)开始扫描 */
while ((p->next!=NULL) && (j<i) /*p不为尾结点且没到i*/
{ p=p->next;
j++;
} /* 扫描下一结点,并计数 */
if(i==j) return p; /* 找到了第i个结点 */
else return NULL; /* 找不到,i≤0或i>n */
}
(2)按值查找:查找是否有值等于e的结点,若有则返回首次找到的结点地址,否则返回NULL。
算法思想:
从第一个结点开始扫描,顺着链逐个将结点的值和给定值e作比较。
Node *Locate(LinkList L,ElemType key) /*查找值为key的结点,找到返回结点地址,否则返回NULL*/
{ Node *p;
p=L->next; /*从第一个结点开始比*/
while (p!=NULL) /*链没完就比对*/
if (p->data!=key)
p=p->next;
else break; /*找到结点key,退出循环*/
return p;
}
这两个算法的时间复杂度都是O(n).
4、求单链表长度
int ListLength(Linklist L)
{
Node *p;
p=L->next;
int j=0;
while(p!=NULL)
{ p=p->next;
j++;
}
return j;
}
5、单链表插入操作
在带头结点的单链表L中第i个元素之前插入元素e。
定位:用指针pre查找第i-1个结点;
构造:构造值为e的新结点并由指针s指示。
插入:新结点抓后链(指针域指向第i个结点),第i-1个结点抓新结点。
void InsList(LinkList L,int i,ElemType e)
{
Node *pre, *s;
pre=L; int k=0;
while(pre!=NULL && k<i-1) /*pre指针定位第i-1个结点*/
{ pre=pre->next;
k=k+1;
}
if(k!=i-1) //链长不足i-1
{ printf("插入位置不合理!");
return;
}
s=(Node*)malloc(sizeof(Node));
s->data=e; //构造待插入结点
s->next=pre->next;
pre->next=s;
return OK;
}
6、单链表删除操作
在单链表L中删除第i个结点。
定位:用指针pre计数扫描找到第i-1个结点,指针r指向第i个结点
绕过:让第i-1个结点连接第i+1个结点,绕过第i个结点
删除:释放第i个结点空间。
int DelList(LinkList L,int i,ElemType *e) /*删除第i个元素,值存入*e中*/
{
Node *pre,*r;
int k=0;
pre=L;
while(pre->next!=NULL && k<i-1) /*寻找结点i前驱,使pre指向它*/
{
pre = pre->next;
k = k+1;
}
if(pre->next==NULL) /* p->next=NULL或i<1跳出循环,删除位置i不合法。*/
{
printf("删除结点的位置i不合理!");
return ERROR;
}
r=pre->next; /*r指向第i个结点*/
pre->next=r->next; /*绕过删除结点r*/
*e=r->data;
free(r); /*释放结点r内存空间*/
return OK;
}
PS:删除算法中的循环条件(pre->next!=NULL && k<i-1)
与前插(pre!=NULL && k<i-1)
的条件不同,因为前插时的插入位置有m+1个(m为数据元素的总数)。i=m+1是指在第m+1个位置插入,即链尾。而删除操作合法位置只有m个,若采用与前插相同的循环条件,则会出现指针指空的情况,使删除操作失败。
7、合并两个有序单链表
将元素非递减有序排列的单链表LA和LB,合并成非递减有序排列的单链表LC。
要求:新表LC利用表LA和LB中的元素结点空间,而不需要额外申请结点空间。
思路:
通过更改结点next域重建结点间的线性关系;
利用尾插法保证新表非递减有序。
例如LA=(2, 2, 3)
, LB=(1, 3, 3, 4)
, 则LC=(1, 2, 2, 3, 3, 3, 4)
。
LinkList MergeLinkList(LinkList LA,LinkList LB)
{
Node *pa=LA->next, *pb=LB->next; //pa、pb跟踪LA、LB表
LinkList LC=LA;
LC->next=NULL;r=LC; //初始化空置LC,借用LA头结点(r指向)
while(pa!=NULL && pb!=NULL) //两表均未处理完,选择较小值结点插入到新表LC中
{ if(pa->data <= pb->data)
{ r->next=pa; r=pa; pa=pa->next;}
else
{ r->next=pb; r=pb; pb=pb->next;}
}
if(pa)
r->next = pa; //表LA未完,后续元素链到新表LC表尾
else
r->next = pb; //否则将表LB中后续元素链到新表LC表尾
free(LB);
return(LC);
}
循环链表
循环链表 :首尾相接的单链表,尾结点指针域指向头结点或第一个结点,形成一个环。
判别p是否为尾指针:p!=L
或p->next!=L
.
用尾指针表示链表,头/尾结点存储位置分别是rear->next->next
和rear
,访问它们的时间复杂度O(1)。
1、初始化循环单链表
InitCLinkList(LinkList *CL)
{
*cl=(*LinkList)malloc(sizeof(Node)); //建立头结点
(*CL) ->next=*CL; //建立空的循环单链表CL
}
2、建立循环单链表
假设线性表中数据类型是字符,以“$”作为输入结束标志符。
void CreateCLinkList(LinkList CL) //采用尾插法
{
Node *s,*rear;
char c;
rear=CL;
c=getchar();
while(c!='$')
{
s=(Node*)malloc((sizeof(Node));
s->data=c;
rear->next=s;
rear=s;
c=getchar();
}
rear->next=CL; //让最后一个结点的next域指向头结点
}
3、循环单链表的合并
合并带头结点的循环单链表LA、LB,合并后头指针为LA,不排序。
思想:
先找到两个链表的尾,由指针p、q指向它们;
将LA的尾(p指向)与LB的第一个结点链接起来;
修改LB的尾q,使它的链域指向LA的头结点。
算法1(采用头指针的循环单链表):
LinkList merge_1(LinkList LA,LinkList LB)
{
Node *p,*q;
p=LA; q=LB;
while(p->next!=LA) p=p->next; /*找到表LA的表尾*/
while(q->next!=LB) q=q->next; /*找到表LB的表尾*/
q->next=LA; /*改表LB尾指针,指向表LA头结点*/
p->next=LB->next; /*改表LA尾指针,指向表LB第一个结点*/
free(LB);
return(LA);
}
执行时间是O(n).
算法2(采用尾指针的循环单链表):
LinkList merge_2(LinkList RA,LinkList RB)
{
Node *p;
p=LA->next; //保存链表RA的头结点地址
RA->next=RB->next->next; //RB的开始结点连在RA的终端结点之后
free(RB->next); //释放RB的头结点
RB->next=p; //RA的头结点连在RB的终端结点之后
return RB; //返回新循环链表的尾指针
}
执行时间是O(1).
双向链表
单链表每个结点增加指向其前趋的指针域prior。
这样形成的链表有两条方向不同的链——双 ( 向) 链表 。
从前往后的操作与单链表相同:求表长、定位、取元素;
方便找前驱结点。
双链表的结点结构 :
结构定义:
typedef struct DNode
{ ElemType data;
struct DNode *prior,*next;
} DNode,* DoubleList;
设p为指向双链表的某一结点,则有下式成立:
p->prior->next==p
;
p==p->next->prior
.
1、双向链表的前插操作
在第i个结点之前插入新结点。
void DlinkIns(DoubleList L,int i,ElemType e)
{ DNode *s, *p;
… /*检查待插入位置i是否合法*/
… /*若位置i合法,则让指针p指向它*/
s=(DNode*)malloc(sizeof(DNode));
if(s)
{ s->data=e;
s->prior=p->prior; (1)
p->prior->next=s; (2) //链接前链
s->next=p; (3)
p->prior=s; (4) //链接后链
return TRUE;
}
else return FALSE;
}
2、双向链表的删除操作
删除双向链表的第i个结点。
int DlinkDel(DoubleList L,int i,ElemType *e)
{ DNode *p;
… /*首先检查待插入的位置i是否合法*/
… /*若位置i合法,则让指针p指向它*/
*e = p->data;//取值
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return TRUE;
}