单链表的扩展操作21----legend050709

单链表的操作之补充:

 

(1). 单链表的反转 :

 

(2). 单链表的排序(插入排序,选择排序,冒泡排序等):

 

(3). 单链表取得最小值的节点,及其前驱:

 

(4). 单链表获取最小的k个节点:

(4-1)单链表的中间节点:

 

(5).单链表节点的追赶问题 :

 

(6). 两个链表公共节点:

(7).单链表是否有环:

(8)两链表的交集,并集 之 2路归并问题 :

 

-------------------------------------------------------------------------------------------

(1). 单链表的反转 :

 

/*带有头节点的单链表的反转

思想: 想让整个链表反转,先将两个节点反转,然后节点的追赶。

 

本来是pnode1->pnode2->pnode3..

然后  逆序为pnode1<-pnode2...

然后         更新pnode1为pnode2;

           更新pnode2为pnode3;(即链表中节点的追赶)

*/

void reverse(LINKLIST linklist){

 

     if(NULL==linklist->next->next || NULL==linklist->next)

     {

           printf("单链表头节点后只有一个节点,或者只有一个头节点,不需要反转\n");

           return ;

     }

 

     NODEPTR pnode1=linklist->next;/*第一个节点*/

     NODEPTR pnode2=pnode1->next;/*第二个节点*/

 

     pnode1->next=NULL;/*第一个节点变成了尾节点*/

 

     NODEPTR pnode3;

     while(pnode2){

     pnode3=pnode2->next;

 

     pnode2->next=pnode1;

 

     pnode1=pnode2;

     pnode2=pnode3;

     }

     /*

     节点的追赶,由于pnode1,pnode2只相差一个节点,所以循环结束时pnode2为null,

     pnode1为之前的尾节点。

     */

 

     linklist->next=pnode1;

 

}

 

(2). 单链表的排序(插入排序,选择排序,冒泡排序等):

         (2.1).选择排序 :

/*与数组的选择排序类似,每次选择出最小的元素放到新的数组中。*/

         /*LINKLIST  ,NODEPTR 是 NODE* 类型的 */

      NODEPTR selectAscentSort(LINKLIST linklist){

 

     NODEPTR pnode=linklist->next;

 

 

     LINKLIST newLinklist;

     initLinkList(&newLinklist);

         /*initLinkList函数是为 了建立一个有头结点的空链表*/

 

     NODEPTR newPNode=newLinklist->next;

 

     NODEPTR pMinmumNode,pPreMinmum;

 

     while(pnode){

     pMinmumNode=minmumNode(linklist,&pPreMinmum);

 

           if(pMinmumNode){

           pPreMinmum->next=pMinmumNode->next;

         //从原有的链表中取掉最小的节点pMinmunNode。

        

         //头插法建立新的链表,不断更新头。

           pMinmumNode->next=newPNode;

           newLinklist->next=pMinmumNode;

           newPNode=pMinmumNode;

 

           pnode=pnode->next;

           }

     }

return newLinklist;

 

}

         (2.2)插入排序 :

/*思想:类似于数组的插入排序,从第2个元素开始往后,将元素逐个插入到前面已经有序的

数组中*/

 

void insertSort(LINKLIST linklist){

 

 

     if(linklist->next)

      {

     NODEPTR pnode_oldList=linklist->next->next;

     /*旧链表从第二个节点开始。*/

     NODEPTR pNode_newList=linklist->next;

     NODEPTR pre_pPnode_newList=linklist;

     pNode_newList->next=NULL;

     NODEPTR pTempNode;

     /*初始时newlist中除了头节点,只有一个节点,就是第一个节点*/

 

     while(pnode_oldList){

 

           NODEPTR pNode_newList=linklist->next;

           NODEPTR pre_pPnode_newList=linklist;

           // 从小到大排序

           while(pNode_newList &&pnode_oldList->data>pNode_newList->data){

            pre_pPnode_newList=pNode_newList;

           pNode_newList=pNode_newList->next;

           }

           /*

           此中注意:空节点的处理,如果原链表中有空节点,则插入一个节点之后还是有空节点的,即使是插在最后一个节点之后。

           因为之后一个节点的next为NULL,即NULL 的前驱为最后一个节点,即使是一个节点插在最后一个节点之后,

            那么NULL 还是会有的。

 

           所以,链表初始化的时候必须给尾节点的next置为NULL。

           */

           /*插入节点*/

           /*注意此中如果pnode_oldList=pnode_oldList->next的位置放在下面注释中,

           则出错,因为在那之前pnode_oldList已经改变了*/

           pTempNode=pnode_oldList;

 

           pnode_oldList=pnode_oldList->next;

 

           pTempNode->next=pNode_newList;

           pre_pPnode_newList->next=pTempNode;

 

           /*pnode_oldList=pnode_oldList->next;*/

     }

 

     }

 

 

}

(3). 单链表取得最小值的节点,及其前驱:

 

 

NODEPTR minmumNode(LINKLIST linklist,NODEPTR * preNode){

 

     NODEPTR pnode;

 

     if(linklist->next){

 

     NODEPTR p_minNode=linklist->next;

     *preNode=linklist;

 

           for(pnode=linklist;pnode->next!=NULL;pnode=pnode->next){

                 if(pnode->next->data<p_minNode->data){

                  *preNode=pnode;

                  p_minNode=pnode->next;

 

                  }

           }

 

           return p_minNode;

     }

 

     return NULL;

 

}

 

(4). 单链表获取最小的k个节点:     

 

         (4.1)思路一: 类似于选择排序中每次获取最小的一个节点,连续获取k次即可。

         (4.2)思路二: 构建最大堆。

 

(5).单链表的追赶问题:

 

         (5.1)求链表的倒数第k个节点:

 

         思想::设置两个指针p1,p2,首先p1和p2 都指向head,然后p2 向前走k 步,这样p1 和p2 之间就间隔k 个节点,最后p1 和p2 同时向前移动,直至p2 走到链表末尾。

 

(5-2)链表的中间节点:

链表的中间节点:
(1)方法一:
遍历一遍,求出长度len,然后在遍历len/2个长度;
(2)方法二:(推荐)
两个速度不一致的指针:
一个指针一次走一个节点,另一个指针一次走两个节点。

总结:
链表的追赶问题,一般的解法都是
1)两个指针,一个走的快,一个走的慢;

2)两个指针,一个先走,一个后走;

(6).两个单链表相交:

 

 (6.1)两个单链表是否相交:

 

(0)      图解:带环相交 与 不带环相交

 

 

 

         分析:两个单链表相交,要么交于尾节点这一个节点,

         要么有多个相交点。

            因为:假设 两个链表只交于中间的某一个节点,该节点后面节点不相交,那么

            这个节点就会有两个next.显然不对。

 

            总结:如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的。

 

         (1)思路一:直接判断list1中的每个节点是否在list2中 .O(n1*n2) :

         n1为list1的节点数;

         。。。

 

         (2)思路二:hashTable,时间复杂度O(n1+n2),

         空间复杂度O(n1) :

 

         针对第一个链表直接构造hash表,然后查询hash 表,判断第二个链表的每个结点是否在hash 表出现,如果第二个链表的结点在hash表中找到,即说明第二个链表与第一个链表有相同的结点。时间复杂度为为线性,

         O(n1+n2)同时为了存储第一个链表的所有节点,空间复杂度为O(n1);

 

         (3)思路三:

                   时间复杂度O(n1+n2);空间复杂度为O(1);

 

         考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。

         所以,先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。

 

         总结:两个链表是否相交 :

 

1.先判断带不带环

2.如果都不带环,就判断尾节点是否相等

3.如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。

如果在,则相交,如果不在,则不相交。

 

代码实现:

/*

1.如果都不带环,就判断尾节点是否相等

2.如果都带环,判断一链表上俩指针相遇的那个节点,

在不在另一条链表上。

*/

bool isIntersected(Node * head1, Node *head2)

{

Node * circleNode1;

Node * circleNode2;

Node * lastNode1;

Node * lastNode2;

bool isCircle1 =isCircle(head1,circleNode1, lastNode1);

bool isCircle2 =isCircle(head2,circleNode2, lastNode2);

//一个有环,一个无环

if(isCircle1 != isCircle2)

return false;

//两个都无环,判断最后一个节点是否相等

else if(!isCircle1 && !isCircle2)

return lastNode1 == lastNode2;

}

//两个都有环,判断环里的节点是否能到达另一个链表环里的节点

else

{

Node * temp = circleNode1->next;

 /*updated*/

while(temp != circleNode1)

{

if(temp == circleNode2)

return true;

temp = temp->next;

}

return false;

}

return false;

}

 

 

         (6.2)求两个链表相交的第一个节点 :

 

 

(1)思路:顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L 个结点,我们先在长的链表上遍历L个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。

 

(2)步骤:

         1.两个链表的长度len1,len2;

         2、在长的链表上先遍历len2-len1之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。

 

(3)代码如下:

ListNode* FindFirstCommonNode( ListNode*pHead1, ListNode *pHead2)

{

// Get the length of two lists

unsigned int nLength1 = ListLength(pHead1);

unsigned int nLength2 = ListLength(pHead2);

int nLengthDif = nLength1 - nLength2;

// Get the longer list

ListNode *pListHeadLong = pHead1;

ListNode *pListHeadShort = pHead2;

if(nLength2 > nLength1)

{

pListHeadLong = pHead2;

pListHeadShort = pHead1;

nLengthDif = nLength2 - nLength1;

}

 

// Move on the longer list

for(int i = 0; i < nLengthDif; ++ i)

pListHeadLong = pListHeadLong->m_pNext;

// Move on both lists

while((pListHeadLong != NULL) &&(pListHeadShort != NULL) && (pListHeadLong !

= pListHeadShort))

{

pListHeadLong = pListHeadLong->m_pNext;

pListHeadShort =pListHeadShort->m_pNext;

}

// Get the first common node in two lists

ListNode *pFisrtCommonNode = NULL;

if(pListHeadLong == pListHeadShort&& pListHeadLong !=NULL)

pFisrtCommonNode = pListHeadLong;

return pFisrtCommonNode;

}

 

 

(7).单链表是否有环:

 

(7.1)单链表是否有环 :

(1)图解:

 

 

(2)思路:设置两个指针(p1, p2),初始值都指向头,

p1 每次前进一步,p2 每次前进二步,如果链表存在环,则p2 先进入环,p1 后进入环,两个指针在环中走动,必定相遇。

 

 

 

(3)代码实现:

/*判断是否带环,如果有环则返回true,并且得到环中的一个节点cycleNode,

如果没有环则得到最后一个节点;

用两个指针,一个指针步长为1,

一个指针步长为2,判断链表是否有环*/

bool isCircle(Node * head, Node *&circleNode, Node *& lastNode)

{

Node * fast = head->next;

Node * slow = head;

while(fast != slow && fast&& slow)

{

 

if(fast->next != NULL)

fast = fast->next;

 

if(fast->next == NULL)

lastNode = fast;

 

if(slow->next == NULL)

lastNode = slow;

 

/*此句是否可以不要?因为如果无环,一定是fast先到结尾。

如果有环,则不存在lastnode;

*/

 

fast = fast->next;

slow = slow->next;

 

}

if(fast == slow && fast &&slow)

{

circleNode = fast;

return true;

}

else

return false;

}

 

         (7.2)寻找环的入口点 :

 

         当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了step步,(step是从起点开始算起的)则fast走了2step步(fast步数还等于s 加上在环上多转的n圈),设环长为cycleLen,则:

 

         2step= step + n*cycleLen;

         所以step=n*cycleLen;

         假设整个链表长为len,入口环与相遇点距离为ruyu;

         起点与环入口点距离为ruqi;

 

         则ruqi+ruyu+m*cycleLen=step=n*cycleLen;

         所以定义ruqi+ruyu=k*cycleLen;

         (k=n-m)

 

         ruqi+ruyu=(k-1)cycleLen+cycleLen

                  =(k-1)cycleLen+len-ruqi;

 

         所以 :ruqi=(k-1)cycleLen+(len-ruqi-ruyu);

 

         len-ruqi-ruyu即为相遇点到环入口点的距离;

 

         所以:从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。

 

         所以代码如下:

Node* FindLoopPort(Node *head)

{

   Node *slow = head, *fast = head;

 

   while ( fast && fast->next )

    {

       slow = slow->next;

       fast = fast->next->next;

       if ( slow == fast ) break;

    }

 

   if (fast == NULL || fast->next == NULL)

       return NULL;

       /*

       此时 slow=fast=相交点

       */

   slow = head;

   while (slow != fast)

    {

        slow = slow->next;

        fast = fast->next;

    }

 

   return slow;

}

 

(8).两链表的交集,并集 之 2路归并问题 :

类似于线性表中,先排序,然后处理。

 

==========================

链表的操作总结:
(1)链表的反转;
(2)链表的两个指针的追赶问题;
比如链表是否有环;获取倒数第k个节点;获取链表的中间节点;

(3)获取链表长度 + 一链表指针先走问题;
比如获取两个链表的第一个相交节点;

(4)两个有序链表的合并;

 

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值