数据结构---单链表

数据结构---单链表

难得有空闲的时间,刚好近段时间在学习算法导论,索性便理一理自己对数据结构的理解.相信大部分初学者都是学习严奶奶版的数据结构,因此,本文便使用该版数据结构中的例子来解释下单链表的插入和删除.管中窥豹,可见一斑,个人看法有限,可供侧面参考.

开门见山,先给出几个定义:

链表结点结构:

Typedef Struct ListNode

{

       ElemType   m_nKey;

       ListNode*  m_pNext;

}*LinkList;

      

几个术语:

       头指针:顾名思义,其只是一个指针,代表指向头结点的指针.

       头结点:头结点一般只有指针域,头结点是处于第一元素节点之前的节点,第一个结点就是第一个含有数据元素的节点.

       首元结点: 第一个含有数据元素的节点.

       三者的图示如下:

链表的插入:

首先,先看下插入过程的图示:

 

从图中,我们可以看出插入一个节点需要经过如下三个步骤:

(1)   找到指向第三个结点的指针,也即找到第二结点.

(2)   让新节点指针域指向原先的第三个结点.

(3)   让第二个结点的指针域指向新的节点.

第一步比较复杂点,其余两步较为简单.

 (1)找到指向指定结点的指针(即找到指定结点的前一个结点).

如果一个链表没有设计尾指针,则该链表唯一的已知条件或者说可用条件只能是头指针,要得到链表中的任意一个节点,都只能从头指针所指向的头结点开始遍历.

说到遍历, 遍历的过程中涉及了很多循环,而在含有循环的代码中经常出现的问题就是在循环结束条件的判断,是该用小于还是小于等于?是该用i还是i-1?对于这方面问题,没有捷径可走,在编写完代码之后,再用边界值、边界值减1、边界值加1等都尝试检查并运行一次.所以,对于第一个步骤来说,难点主要在于遍历过程中循环的控制.

那么,首先考虑下遍历的逆推过程,要找到指向指定节点的指针→该指针保存在其前一个结点的指针域中→要找到指向前一个结点的指针→该指针保存在其前一个结点的指针域中→…..→找到头指针.因此,当定义一个遍历过程中的指针ListNode* pCur时,毫无疑问,初始值就为head指针(头指针),上面逆推的终点,即ListNode*pCur=head;. 除此之外,还需要一个int型的变量j来记录当前位置,那么又需要考虑一个问题,初始位置的值该设为多少呢?其实,在链表中,在计算节点总数时,头结点并没有参与计数.所以,我们可以认为当前头结点的位置为0;这样一来,当前指针pCur指向头结点,位置为0.当改变pCur指向时,对j加一,这是就会出现pCur指向第一个结点,j=1;两者始终保持一致.可以通过下面的例子来理解.

条件是头指针Head,要查找结点的位置为i,那么可以设计出如下的遍历方式:

ListNode* TraverList(LinkList head,int i)

{

      ListNode*pCur=head;

      int j=0;

      while(pCur->m_Next!=NULL && j<i)

{

       pCur=pCur->pNext;

       j++;

}

Return pCur;

}

当还未开始循环时,pCur是头指针,而这时候位置值j为0,那么我们就把头结点认为是第0个结点,因为这样的话,指针和位置值就能够始终保持一致,也是一个循环不定式,看下循环过程(假设结点总数为10,i=5):

       第一次循环结束:pCur指向第一个结点,j=1 当前确实为第一个结点;

       第二次循环结束:pCur指向第二个结点,j=2 当前确实为第二个结点;

       第三次循环结束:pCur指向第三个结点,j=3 当前确实为第三个结点;

       第四次循环结束:pCur指向第四个结点,j=4 当前确实为第四个结点;

       第五次循环结束:pCur指向第五个结点,j=5 当前确实为第五个结点;

       第六次…………:因为j==i了.所以无法进入循环,就没有在改变pCur的指向了,即指向第五个结点,可见是正确的。

       首先, pCur->m_Next !=NULL只是为了能够在从第一个结点遍历到最后一个结点所设置的条件,因为最后一个结点的指针域为空.

       其次,为什么使用j<i而不是j<=i呢?其实,从上面可以看出来,因为j的初值为0,所以使用j<i,刚好可以执行i次循环,而不是i-1次循环,而因为i的值始终和pCur保持保持一致,因此,可以保证每次pCur的指向刚好是第i个结点.或许从另一个角度来看, 那么为什么不是j<=i呢?假设我们i=3,那也就是说要找到第三个结点,当第二次循环结束时,j=2,这时还能进入循环,一旦进入循环,也就意味着pHead就从原来的第二个结点指向第三个结点,而j也等于3,这时候就不需要在进入循环了,因为我们需要的结果已经得到.如果这时j<=3,就能再次进入循环,就导致指向了第四个结点,就不是我们想要的结果了。(也就是说,当我们想要第N个几点时,我们要保证能够刚好进入第N次循环(初始值为0),那么也是这次循环将原本的pCur指向第N-1结点设为第N个结点)

       最后,初始值j设置为0,只是为了可以和pCur保持一致,因为pCur的初始值是指向头结点,头结点并不能算入元素节点,因此刚好以0来计数.

 

(2)  让新结点指针域指向指定结点.

设新结点为: ListNodes=malloc(sizeof(ListNode))

s->m_pNext=pCur->m_pNext;

(3)  让指定结点的前一个结点的指针域指向新节点.

pCur->m_pNext=s;

 

可以写出最终的插入函数为:

StatusListInsert_L(LinkList* pListHead,int i,ElemType e)

{

       if(pListHead==NULL)

{

       return  error;

}

 

//定义开始遍历的指针和要插入位置的前一个位置j

ListNode* pCur=pListHead;

Int j=0;

 

//寻找i结点的前一个结点,即第i-1个结点(也即表示找到指向该结点的指针)

While(pCur->m_pNext !=NULL &&j<i-1)

{

       pCur=pCur->m_pNext;

       j++;

}

 

//判断是否成功

If(!pCur || j>i-1)

{

       Return  error;

}

 

//步骤二和步骤三

If((ListNodes=malloc(sizeof(ListNode)))!=NULL)

{

       s->m_nKey=e;

       s->m_pNext=pCur->m_pNext;     步骤二

       pCur->m_pNext=s;                步骤三

       return  OK;

}

}

链表的删除:

链表的删除原理和插入大同小异,不在赘述.其最终代码如下:

Statue ListDelete_L(ListNode* pListHead,int I,ElemType &e)

{

       if(pListHead==NULL)

       {

              return  error;

}

ListNode* pCur=pListHead;

Int j=0;

 

//寻找i结点的前驱结点,即第i-1个结点(也即表示找到指向该结点的指针)

While(pCur->m_pNext !=NULL &&j<i-1)

{

       pCur=pCur->m_pNext;

       j++;

}

ListNode temp=pCur->m_pNext;

e=pCur->m_nKeys;

pCur->m_pNext=temp->m_pNext;

free(temp);

return OK;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值