絮絮叨叨
如何轻松写链表的代码?
- 有决心并付出精力
- 理解指针或引用的含义
- 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
- 警惕指针丢失和内存泄漏
- 利用哨兵(头结点)简化实现难度
- 重点留意边界条件处理
- 如果链表为空时,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
- 举例画图,辅助思考
一、数据结构
1、单向链表
- 结点包括:数据域 + 指针域
- 数据域:存储数据元素的值
- 指针域(链域):存储下一个结点地址或者指向其后继结点的指针
// 定义结点 Node
typedef struct Node{
ElemType data;
struct Node * next;
} Node;
// 定义指向结点 Node 类型对象的指针 LinkList
typedef struct Node *LinkList;
2、双向链表
- 结点包括:数据域 + 左指针域(prev) + 右指针域(next)
struct DNode{
int data;
DNode * prev;
DNode * next;
}
二、基本操作实例
1、单链表的读取
(1)获取链表第 i 个数据结点的算法思路:
- 声明一个指针 p 指向链表的第一个结点,初始化 j 从 1 开始;
- 当 j < i 时,遍历链表,p 不断指向下一个结点, j++;
- 若到链表末尾 p 为空, 则说明第 i 个元素不存在;
- 否则查找成功,返回结点 p 的数据。
(2)实现
由于单链表的结构中没有定义表长,所以不能事先知道要循环多少次,因此不方便用for循环来控制循环。==》while循环
# define OK 1
# define ERROR 0
# define TRUE 1
# define FALSE 0
typdef int Status; //Status是函数的类型,其值为函数结果状态码,eg:OK等
/*初始条件:顺序线性表L已存在,1≤i≤ListLength(L)*/
/*操作结果: 用e返回L中第i个数据元素的值*/
Status GetElem( Node *L, int i, Elemtype *e){
int j;
LinkList p; // 声明指针p
p = L->next; //让p指向链表L的第一个节点
j = 1;
while(p && j<i) //当p不为空 或 j 小于i时,继续循环
{
p = p->next;
++j;
}
if(!p || j>i )
return ERROR;
*e = p->data;
return OK;
}
2、插入结点(单向链表)
s->next = p->next;
p->next = s
(1)第 i 个数据插入结点的算法思路:
- 声明一个指针p指向链表的第一个结点,初始化j=1;
- 当 j < i 时,遍历链表,让指针 p 向后移动,不断指向下一结点,++j;
- 若到链表末尾 p 为空,说明第 i 个元素不存在;
- 否则查找成功,在系统中生成一个空结点 s;
- 将数据元素 e 赋值给 s->data;
- 单链表的插入标准语句:s->next = p->next; p->next = s
- 返回成功
(2)实现
/*初始条件:顺序线性表L已存在,1≤i≤ListLength(L)*/
/*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1*/
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while(p && j<i) //寻找第i个结点
{
p = p->next;
++j;
}
if(!p || j>i ) // 第i个结点不存在
return ERROR;
s = (LinkList)malloc(sizeof(Node)); //生成新的结点
s->data = e;
s->next = p->next; //将p的后继结点赋值给s的后继
p->next = s; //将s赋值给p的后继
return OK;
}
3、删除结点(单向链表)
p->next = p->next->next
用q取代p->next的话,上面等价于:
q = p->next;
p->next=q->next
(1)第 i 个数据删除结点的算法思路:
- 声明一个指针p指向链表的第一个结点,初始化 j 从1开始;
- 当 j<i 时,遍历链表,让指针p向后移动,不断,指向下一结点,++j;
- 若到链表末尾p为空,说明第i个结点不存在;
- 否则查找成功,将欲删除的结点p->next赋值给q;
- 单链表的删除标准语句:p->next = q->next;
- 将q结点中的数据赋值给e,作为函数的返回值
- 释放q结点
- 返回成功
(2)实现
#include<stdio.h>
Status ListDelete(LinkList *L, int i, ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
while(p->next && j<i){ //遍历查找第i个结点
p = p->next;
++j;
}
if(!(p->next) || j>i)
return ERROR;
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
4、单链表的整表创建
单链表的整表创建过程就是一个动态生成链表的过程。由“空表”的初始状态,依次建立各元素结点,并逐个插入链表。
(1)算法思路(头插法):
- 声明一结点 p 和 计数器变量 i;
- 初始化一空链表 L;
- 让 L 的头结点的指针指向NULL,即建立一个带头结点的单链表;
- 循环:
- 生成一个新结点赋值给 p;
- 随机生成一个数组赋值给 p 的数据域 p->data;
- 将 p 插入到头结点与前一新结点之间。
(2)实现
头插法
/* 随机产生n个元素的值,建立带头结点的单链表L */
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); //初始化随机种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //建立一个带头结点的单链表
for(i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->data = rand()%100 + 1;
p->next = (*L)->next;
(*L)->next = p;
}
}
尾插法
/* 随机产生n个元素的值,建立带头结点的单链表L */
void CreateListHead(LinkList *L, int n)
{
LinkList p, r;
int i;
srand(time(0)); //初始化随机种子
*L = (LinkList)malloc(sizeof(Node));
r = *L; // *r 指向尾部的结点
for(i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->data = rand()%100 + 1;
r->next = p;
r = p;
}
r->next=NULL;
}
5、单链表的整表删除
(1)算法思路
- 声明结点结点 p 和 q;
- 将第一个结点赋值给 p;
- 循环:
- 将下一结点赋值给 q;
- 释放 p;
- 将 q 赋值给 p;
(2)实现
/*初始条件:顺序线性表L已存在,操作结果:将L充值为空表*/
Status CLearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
三、常见操作
1、单链表反转
法一:反向遍历链表就类似于事先遍历的节点后输出,即“先进后出”,那么可以将链表遍历存放于栈中,其后遍历栈依次弹出栈节点,达到反向遍历效果。
//1.stack
void printLinkedListReversinglyByStack(Node *head){
stack<Node* > nodesStack;
Node* pNode = head;
//遍历链表
while (pNode != NULL) {
nodesStack.push(pNode);
pNode = pNode->next;
}
while (!nodesStack.empty()) {
pNode=nodesStack.top();
printf("%d\t", pNode->value);
nodesStack.pop();
}
}
//2.递归
void printLinkedListReversinglyRecursively(Node *head){
if (head!=NULL) {
if (head->next!=NULL) {
printLinkedListReversinglyRecursively(head->next);
}
printf("%d\t", head->value);
}
}
2、链表中环的检查,获取连接点,计算环的长度
判断链表是否有环路,获取连接点,计算环的长度
此题很有意思,具体详细请参考:http://www.cnblogs.com/xudong-bupt/p/3667729.html
判断是否含有环:slow和fast,slow指针每次走一步,fast指针每次走两步,若是链表有环,fast必能追上slow(相撞),若fast走到NULL,则不含有环。
//判断是否含有环
bool containLoop(Node* head){
if (head==NULL) {
return false;
}
Node* slow = head;
Node* fast = head;
while (slow!=fast&&fast->next!=NULL) {
slow = slow->next;
fast = fast->next->next;
}
if (fast==NULL) {
return false;
}
return true;
}
判断环的长度:在相撞点处,slow和fast继续走,当再次相撞时,slow走了length步,fast走了2*length步,length即为环得长度。
//获得环的长度
int getLoopLength(Node* head){
if (head==NULL) {
return 0;
}
Node* slow = head;
Node* fast = head;
while (slow!=fast&&fast->next!=NULL) {
slow = slow->next;
fast = fast->next->next;
}
if (fast==NULL) {
return 0;
}
//slow和fast首次相遇后,slow和fast继续走
//再次相遇时,即slow走了一圈,fast走了两圈
int length = 0;
while (slow!=fast) {
length++;
slow = slow->next;
fast = fast->next->next;
}
return length;
}
环得连接点:slow和fast第一次碰撞点到环的连接点的距离=头指针到环的连接点的距离,此式可以证明,详见上面链接。
//获得环的连接点
Node* getJoinpoit(Node* head){
if (head==NULL) {
return NULL;
}
Node* slow = head;
Node* fast = head;
while (slow!=fast&&fast->next!=NULL) {
slow = slow->next;
fast = fast->next->next;
}
if (fast==NULL) {
return NULL;
}
Node* fromhead = head;
Node* fromcrashpoint = slow;
while (fromcrashpoint!=fromhead) {
fromhead = fromhead->next;
fromcrashpoint = fromcrashpoint->next;
}
return fromhead;
}
3、找出中间节点
用slow和fast指针标记,slow每次走一步,fast每次走两步,当fast到尾节点时,slow就相当于总长度的一半,即在中间节点。
//找出中间节点
Node* findMidNode(Node* head){
Node* slow = head;
Node* fast = head;
while (fast->next != 0&&fast->next->next!=0) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
4、找出倒数第k个节点
用slow和fast指针标记,fast指针事先走k步,然后slow和fast同时走,当fast到达末节点时,slow在fast的前k个节点,即为倒数第k个节点。
//找出倒数第k个节点
Node* findKNode(Node* head,int k){
Node *temp1 = head;
Node *temp2 = head;
while (k-->0) {
if(temp2 == NULL){
return NULL;
}
temp2 =temp2->next;
}
while (temp2->next != NULL&&temp2->next->next!=NULL) {
temp1 = temp1->next;
temp2 = temp2->next->next;
}
return temp1;
}