您的位置:首页 > 理论基础 > 数据结构算法

单链表

2016-06-29 16:22 274 查看
单链表,顾名思义是线性表的链式存储结构,是用一组任意的存储单元存储线性表的数据元素(这个存储单元可以是连续的,也可以是不连续的)。单链表的单位是节点,每个节点包括两个域,其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域,指针域中存储的信息叫做指针。如图:



节点的指针域存储的是指向下一个元素的地址,因此我们可以将链表画成用箭头相链接的节点的序列,节点之间的箭头表示指针。如图:



第一个节点由一个头指针指向,因此单链表可由头指针唯一确定,每一个节点的指针域指向下一个节点,最后一个节点指针域为NULL。若单链表需要用于计算节点总数,可在头指针后添加头结点,头结点数据域为单链表的节点个数,指针域指向第一个节点。

单链表的结构体声明和定义:

C:

typedef struct Node{
int data;
struct Node *next;
}LNode,*LinkList;
相当于:

typedef struct Node{
int data;
struct Node *next;
}LNode;
typedef struct Node *LinkList;
LNode是struct Node的别名,是单链表节点类型。ListNode是struct Node *,是指向单链表节点的指针类型。

C++

struct Node{
int data;
Node *next;
};
单链表初始化(头插法)://每次新增数据都插在头结点后

LinkList InitList(LinkList &L,int n)
{
L=(LinkList)malloc(sizeof(LNode));//新建头节点,L是头指针,*L是头结点
L->next=NUll;
for(int i=n;i>0;i--)
{
LinkList p=(LinkList)malloc(sizeof(LNode));
sanf(&p->data);
p->next=L->next;
L->next=p;
}
return L;
}


(尾插法)://每次新增数据都插入在尾节点后

LinkList InitList(LinkList &L,int n)
{
L=(LinkList)malloc(sizeof(LNode));
L->next=NUll;
LinkList r=L;//尾指针,指向链表最后一个节点
for(int i=n;i>0;i--)
{
LinList p=(LinkList)malloc(sizeof(LNode));
scanf(&p->data);
r->next=p;
r=p;
}
r->next=NULL;
return L;
}
单链表的插入

单链表第i个数据之前插入数据的算法思路:

1.声明一个指针p,指向头节点,设置一个定位j=1;

2.遍历单链表,若(j<i),则p=p->next,j++;

3.遍历结束后,若p为空,则表示单链表中没有第(i-1)个元素,否则,指针p指向单链表第(i-1)个元素;

4.新建一个指向空节点的指针s,输入数据域s->data,且令s-next=p->next,p->next=s。

5.返回头指针。

LinkList insertList(LinkList &L,int i,int n)
{
LinkList p=L;
int j=1;
while(p&&j<i)
{
p=p->next;
j++;
}
if(!p||j>i)return NULL;
LinkList s=(LinkList)malloc(sizeof(LNode));
s->next=p-next;
p-next=s;
return L;
}

单链表的删除:

单链表删除第i个元素的算法思路:

1.声明一个指针指向头结点p=L,并设置j=0;

2.遍历单链表,当(j<i-1)时,令p=p->next且j++;

3.遍历结束后,此时p指向第(i-1)个节点,若p->next为空,则无第(i)个元素,否则删除;

4.声明一个指针指向p的后继节点,s=p->next,此时s指向第(i)个节点。

5.p-next=s-next,且使用free(s)回收该节点,释放内存。

LinkList deleteList(LinkList &L,int i)
{
LinkList p=L;
int j=0;
while(p->next&&j<i-1)
{
p=p->next;
j++;
}
if(!(p->next)||j>i-1) return NULL;
LinkList q=p->next;
p->next=q->next;
free(q);
return L;
}
总结:要在第i个节点之前插入节点以及删除第i个节点,都需要找到第(i-1)个节点,不同在于插入时要判断第(i-1)节点非空,删除要判断第(i)个节点非空。插入算法和删除算法的时间复杂度均为线性级。

获取单链表指定位置元素:

1.声明一个指针p指向头结点,设置定位j=0;

2.遍历单链表,当下一个节点非空且(j<i),令p=p->next,且j++;

3.遍历结束后,若p为空,则无第(i)个节点,否则p指向第(i)个节点;

4。获取节点数据域,e=p->data,返回改值。

Status getList(LinkList L,int i,int&e)
{
LinkList p=L;
int j=0;
while(p&&j<i)
{
p=p->next;
j++;
}
if(!(p)||j>i) return ERROR;
e=p->data;
return OK;
 }
查找元素在链表中的位置:
int locataList(LinkList L,int e)
{
LinkList p=L;
int j=0;
while(p->next)
{
p=p->next;
j++;
if(e==p->data)return j;
}
return 0;
}
置空单链表:

1.遍历单链表,若头节点有后继节点,则令p=L->next,L->next=p->next,回收指针p指向的节点,free(p)。

2.遍历结束后,头结点的指针域为NULL。返回成功。

Status clearList(LinkList &L)
{
while(L->next)
{
LinkList p=L->next;
L->next=p->next;
free(p);
 }
return OK;
 }

或者:

1.设置一个工作指针p=L->next,指向第一个节点。

2.遍历单链表,若当前节点非空,则设置一个临时指针指向当前节点后继节点q=->next,释放当前节点p,并令p=q,更新工作指针。

3.遍历结束后,已经释放了所有节点内存,则令L->next=NULL。返回成功。

Status clearList(LinkList &L)
{
LinkList p=L->next;
while(p)
{
LinkList q=p->next;
free(p);
p=q;
}
L->next=NULL;
return OK;

}

反转单链表:

思路:每次将第二个节点先从第一个节点后断开,再使用头插法插入链表。

1.设置一个工作指针p始终指向第一个节点,p=L->next;

2.若当前节点p的后继节点q存在时,循环单链表,令指针q指向当前节点p的后继节点q=p->next;

3.令q后的链串成为p的后继,p->next=q->next,此时q与p断开,将q插入到头结点后,q->next=L->next,L->next=q。

4.结束循环,链表重置结束。

Status reverseList(LinkList &L)
{
if(L==NULL)return NULL;
LinkList first=L->next;
while(first->next)
{
LinkList second=first->next;
first->next=second->next;
second->next=L->next;
L->next=second;
}
return L;
}

单链表倒序第i个元素:[/b]

思路一:上述说道有头结点的单链表可以将头结点的数据域设置为单链表的元素个数,即L->data=n;则倒序第i个元素则是正序第(n-i+1)个元素,只需要设置一个工作指针指向第一个元素p=L->next,且令p后移(n-i)次则,p此时指向所求元素。

Status getListFromBack(LinkList L,int i,ElemType *e)
{
LinkList p=L->next;
int n=L->data;
int j=1;
if(n<i)return ERROR;
while(p->next&&j<(n-i+1))
{
p=p->next;
j++;
}
if(!p)return ERROR;
*e=p->data;
return OK;
}
思路二:但是更多时候头结点的数据域为空,我们当然可以先遍历一遍单链表计算节点总数后采用思路一算法。但是如此太过繁琐,我们可以设置两个工作指针完成一次遍历过程便可以找到所求节点。

1.声明一个工作指针指向头结点,p=L;令p正序移动(i-1)次,判断(p->next)是否为NULL,若是则超过链表长度。

2.若未超过链表长度,则再声明第二个工作指针指向头结点,q=L;令p,q同时移动,当p->next=NULL,即p指向链表为尾节点时,q指向的即是所求倒序第i个节点。

Status getListFromBack(LinkList L,int i,ElemType *e)
{
LinkList first=L;
int j=1;
while(j<i&&first->next)//走i-1次
{
j++;
first=first->next;
}
if(first->next==NULL&&j<i-1)return ERROR;
LinkList second=L;
while(first->next)
{
first=first->next;
second=second->next;
}
*e=second->data;
return OK;
}
合并两个有序链表:

1.声明一个指向新的头结点的指针,C=(LinkList)malloc(sizeof(LNode)),该链表用来存储合并后的有序链表,并设置该链表尾指针R=C;

2.利用数组归并排序的思想,从两个有序链表A,B的头结点开始,分别给两个有序链表设置工作指针pa,pb指向当前元素,直至其中一条链表置空。

3.将另外一条链表的剩下元素添加至链表C的尾节点后。

LinkLis mergeList(LinkList &A,LinkList &B)
{
LinkList C=pc=A;//用A的头结点作为C的头结点,pc是C的尾节点
LinkList pa=A->next;
LinKList pb=B->next;
while(pa&&pb)
{
if(pa->data<=pb->data)
{
pc->next=pa;
pc=pa;//更新C尾指针,等同于pc=pc->next
pa=pa->next;//更新A链表当前指针
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;//在C尾指针后添加非空链表剩下部分
free(B);
return C
}

如何判断链表有环:

可以设置两个工作指针,起点均从头结点开始,但是其中指针每次后移一步,另外一个指针每次后移两步,若有环,则两个指针最终会指向同一个节点。

bool isLoopList(LinkList L)
{
LinkList p=L;
LinkList q=L;
while(q->next&&q->next->next)
{
p=p->next;
q=q->next->next;
if(p==q)
return true;
}
return false;
}

拓展:如何找到环的入口呢?

思路:当快慢指针相遇时,慢指针走了s步,快指针走了2s步,当两个指针相遇时,快指针在环内绕了n圈,设环的长度为r,则2s=s+nr,因此s=nr。

假设链表长度为L,环入口到相遇点距离为a,头节点到环入口距离为x,头节点到相遇点距离已知为s,因此:

a+x=s=nr=r(n-1)+r=r(n-1)+(L-x)

x=r(n-1)+(L-x-a)

因此:当快慢指针相遇时,我们再设置一个慢指针2,同样每次前进一步,当两个慢指针相遇时,慢指针2号便指向环的入口。

寻找链表中间节点:

思路一:可以先遍历一次单链表确定链表长度L,然后再从头结点开始移动L/2次工作指针寻找中间节点。

思路二:可以设置两个工作指针,第一个指针每次后移一步,另外一个指针后移两步。

LinkList midList(LinkList L)
{
LinkList p=L;
LinkList q=L;
while(q->next!)//循环出口
{
if(q->next->next)
{
q=q->next->next;
p=p->next;
}
else
{
q=q->next;
}
}
return p;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据结构 单链表 ADT