連結串列操作

ZY_FlyWay發表於2017-12-06

  連結串列概述
   連結串列是一種常見的重要的資料結構。它是動態地進行儲存分配的一種結構。它可以根據需要開闢記憶體單元。連結串列有一個“頭指標”變數,以head表示,它存放一個地址。該地址指向一個元素。連結串列中每一個元素稱為“結點”,每個結點都應包括兩個部分:一為使用者需要用的實際資料,二為下一個結點的地址。因此,head指向第一個元素:第一個元素又指向第二個元素;……,直到最後一個元素,該元素不再指向其它元素,它稱為“表尾”,它的地址部分放一個“NULL”(表示“空地址”),連結串列到此結束。
        連結串列的各類操作包括:學習單向連結串列的建立、刪除、  插入(無序、有序)、輸出、  排序(選擇、插入、冒泡)、反序等等。

       單向連結串列的圖示:
       ---->[NULL]
      head

      圖1:空連結串列

       ---->[p1]---->[p2]...---->[pn]---->[NULL]
      head   p1->next  p2->next   pn->next

      圖2:有N個節點的連結串列

      建立n個節點的連結串列的函式為:

[cpp] view plain copy
  1. #include "stdlib.h"  
  2. #include "stdio.h"  
  3.   
  4. #define NULL 0  
  5. #define LEN sizeof(struct student)  
  6.   
  7. struct student  
  8. {  
  9.     int num;              //學號   
  10.     float score;          //分數,其他資訊可以繼續在下面增加欄位  
  11.     struct student *next;       //指向下一節點的指標  
  12. };  
  13.   
  14. int n;  //節點總數   
  15. /* 
  16. ========================== 
  17. 功能:建立n個節點的連結串列 
  18. 返回:指向連結串列表頭的指標 
  19. ========================== 
  20. */  
  21. struct student *Create()  
  22. {  
  23.     struct student *head;       //頭節點  
  24.     struct student *p1 = NULL;  //p1儲存建立的新節點的地址  
  25.     struct student *p2 = NULL;  //p2儲存原連結串列最後一個節點的地址  
  26.   
  27.     n = 0;          //建立前連結串列的節點總數為0:空連結串列  
  28.     p1 = (struct student *) malloc (LEN);   //開闢一個新節點  
  29.     p2 = p1;            //如果節點開闢成功,則p2先把它的指標儲存下來以備後用  
  30.   
  31.     if(p1==NULL)        //節點開闢不成功  
  32.     {  
  33.         printf ("\nCann't create it, try it again in a moment!\n");  
  34.         return NULL;  
  35.     }  
  36.     else                //節點開闢成功  
  37.     {  
  38.         head = NULL;        //開始head指向NULL  
  39.         printf ("Please input %d node -- num,score: ", n + 1);  
  40.         scanf ("%d %f", &(p1->num), &(p1->score));    //錄入資料  
  41.     }  
  42.     while(p1->num != 0)      //只要學號不為0,就繼續錄入下一個節點  
  43.     {  
  44.         n += 1;         //節點總數增加1個  
  45.         if(n == 1)      //如果節點總數是1,則head指向剛建立的節點p1  
  46.         {  
  47.             head = p1;  
  48.             p2->next = NULL;  //此時的p2就是p1,也就是p1->next指向NULL。  
  49.         }  
  50.         else  
  51.         {  
  52.             p2->next = p1;   //指向上次下面剛剛開闢的新節點  
  53.         }  
  54.   
  55.         p2 = p1;            //把p1的地址給p2保留,然後p1產生新的節點  
  56.   
  57.         p1 = (struct student *) malloc (LEN);  
  58.         printf ("Please input %d node -- num,score: ", n + 1);  
  59.         scanf ("%d %f", &(p1->num), &(p1->score));  
  60.     }  
  61.     p2->next = NULL;     //此句就是根據單向連結串列的最後一個節點要指向NULL  
  62.   
  63.     free(p1);           //p1->num為0的時候跳出了while迴圈,並且釋放p1  
  64.     p1 = NULL;          //特別不要忘記把釋放的變數清空置為NULL,否則就變成"野指標",即地址不確定的指標  
  65.     return head;        //返回建立連結串列的頭指標   
  66. }  

      輸出連結串列中節點的函式為:

[cpp] view plain copy
  1. /* 
  2. =========================== 
  3.  功能:輸出節點 
  4.  返回: void 
  5. =========================== 
  6. */  
  7. void Print(struct student *head)  
  8. {  
  9.     struct student *p;  
  10.     printf ("\nNow , These %d records are:\n", n);  
  11.     p = head;  
  12.     if(head != NULL)        //只要不是空連結串列,就輸出連結串列中所有節點  
  13.     {  
  14.         printf("head is %o\n", head);    //輸出頭指標指向的地址  
  15.         do  
  16.         {  
  17.             /* 
  18.             輸出相應的值:當前節點地址、各欄位值、當前節點的下一節點地址。 
  19.             這樣輸出便於讀者形象看到一個單向連結串列在計算機中的儲存結構,和我們 
  20.             設計的圖示是一模一樣的。 
  21.             */  
  22.             printf ("%o   %d   %5.1f   %o\n", p, p->num, p->score, p->next);  
  23.             p = p->next;     //移到下一個節點  
  24.         }  
  25.         while (p != NULL);  
  26.     }  
  27. }  

       單向連結串列的刪除圖示:
       ---->[NULL]
       head

       圖3:空連結串列

      從圖3可知,空連結串列顯然不能刪除

      ---->[1]---->[2]...---->[n]---->[NULL](原連結串列)
      head   1->next  2->next   n->next

      ---->[2]...---->[n]---->[NULL](刪除後連結串列)
      head   2->next   n->next

      圖4:有N個節點的連結串列,刪除第一個節點
      結合原連結串列和刪除後的連結串列,就很容易寫出相應的程式碼。操作方法如下:
      1、你要明白head就是第1個節點,head->next就是第2個節點;
       2、刪除後head指向第2個節點,就是讓head=head->next,OK這樣就行了。

       ---->[1]---->[2]---->[3]...---->[n]---->[NULL](原連結串列)
       head   1->next  2->next  3->next   n->next

       ---->[1]---->[3]...---->[n]---->[NULL](刪除後連結串列)
      head   1->next  3->next   n->next

      圖5:有N個節點的連結串列,刪除中間一個(這裡圖示刪除第2個)
      結合原連結串列和刪除後的連結串列,就很容易寫出相應的程式碼。操作方法如下:
      1、你要明白head就是第1個節點,1->next就是第2個節點,2->next就是第3個節點;
      2、刪除後2,1指向第3個節點,就是讓1->next=2->next。

      刪除指定學號的節點的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:刪除指定節點 
  4.   (此例中是刪除指定學號的節點) 
  5.  返回:指向連結串列表頭的指標 
  6. ========================== 
  7. */  
  8. struct student *Del (struct student *head, int num)  
  9. {  
  10.     struct student *p1;     //p1儲存當前需要檢查的節點的地址  
  11.     struct student *p2;     //p2儲存當前檢查過的節點的地址  
  12.     if (head == NULL)       //是空連結串列(結合圖3理解)  
  13.     {  
  14.         printf ("\nList is null!\n");  
  15.         return head;  
  16.     }  
  17.   
  18.     //定位要刪除的節點  
  19.     p1 = head;  
  20.     while (p1->num != num && p1->next != NULL)    //p1指向的節點不是所要查詢的,並且它不是最後一個節點,就繼續往下找  
  21.     {  
  22.         p2 = p1;            //儲存當前節點的地址  
  23.         p1 = p1->next;       //後移一個節點  
  24.     }  
  25.   
  26.     if(p1->num==num)     //找到了。(結合圖4、5理解)  
  27.     {  
  28.         if (p1 == head)     //如果要刪除的節點是第一個節點  
  29.         {  
  30.             head = p1->next; //頭指標指向第一個節點的後一個節點,也就是第二個節點。這樣第一個節點就不在連結串列中,即刪除  
  31.         }  
  32.         else            //如果是其它節點,則讓原來指向當前節點的指標,指向它的下一個節點,完成刪除  
  33.         {  
  34.             p2->next = p1->next;  
  35.         }  
  36.   
  37.         free (p1);      //釋放當前節點  
  38.         p1 = NULL;  
  39.         printf ("\ndelete %ld success!\n", num);  
  40.         n -= 1;         //節點總數減1個  
  41.     }  
  42.     else                //沒有找到  
  43.     {  
  44.         printf ("\n%ld not been found!\n", num);  
  45.     }  
  46.   
  47.     return head;  
  48. }  

       單向連結串列的插入圖示:
       ---->[NULL](原連結串列)
      head

      ---->[1]---->[NULL](插入後的連結串列)
      head   1->next

      圖7 空連結串列插入一個節點
      結合原連結串列和插入後的連結串列,就很容易寫出相應的程式碼。操作方法如下:
     1、你要明白空連結串列head指向NULL就是head=NULL;
     2、插入後head指向第1個節點,就是讓head=1,1->next=NULL,OK這樣就行了。

     ---->[1]---->[2]---->[3]...---->[n]---->[NULL](原連結串列)
     head   1->next  2->next  3->next   n->next

     ---->[1]---->[2]---->[x]---->[3]...---->[n]---->[NULL](插入後的連結串列)
     head   1->next  2->next  x->next  3->next   n->next

     圖8:有N個節點的連結串列,插入一個節點(這裡圖示插入第2個後面)
     結合原連結串列和插入後的連結串列,就很容易寫出相應的程式碼。操作方法如下:
    1、你要明白原1->next就是節點2,2->next就是節點3;
    2、插入後x指向第3個節點,2指向x,就是讓x->next=2->next,1->next=x。

    插入指定節點的後面的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:插入指定節點的後面 
  4.   (此例中是指定學號的節點) 
  5.  返回:指向連結串列表頭的指標 
  6. ========================== 
  7. */  
  8. struct student *Insert (struct student *head, int num, struct student *node)  
  9. {  
  10.     struct student *p1;     //p1儲存當前需要檢查的節點的地址  
  11.     if (head == NULL)       //(結合圖示7理解)  
  12.     {  
  13.         head = node;  
  14.         node->next = NULL;  
  15.         n += 1;  
  16.         return head;  
  17.     }  
  18.   
  19.     p1 = head;  
  20.     while(p1->num != num && p1->next != NULL)  //p1指向的節點不是所要查詢的,並且它不是最後一個節點,繼續往下找  
  21.     {  
  22.         p1 = p1->next;       //後移一個節點  
  23.     }  
  24.   
  25.     if (p1->num==num)        //找到了(結合圖示8理解)  
  26.     {  
  27.         node->next = p1->next;    //顯然node的下一節點是原p1的next  
  28.         p1->next = node;     //插入後,原p1的下一節點就是要插入的node  
  29.         n += 1;         //節點總數增加1個  
  30.     }  
  31.     else  
  32.     {  
  33.         printf ("\n%ld not been found!\n", num);  
  34.     }  
  35.     return head;  
  36. }  

       單向連結串列的反序圖示:
       ---->[1]---->[2]---->[3]...---->[n]---->[NULL](原連結串列)
       head   1->next  2->next  3->next   n->next

      [NULL]<----[1]<----[2]<----[3]<----...[n]<----(反序後的連結串列)
                1->next  2->next  3->next   n->next  head

          圖9:有N個節點的連結串列反序
          結合原連結串列和插入後的連結串列,就很容易寫出相應的程式碼。操作方法如下:
          1、我們需要一個讀原連結串列的指標p2,存反序連結串列的p1=NULL(剛好最後一個節點的next為NULL),還有一個臨時儲存變數p;
          2、p2在原連結串列中讀出一個節點,我們就把它放到p1中,p就是用來處理節點放置順序的問題;
          3、比如,現在我們取得一個2,為了我們繼續往下取節點,我們必須儲存它的next值,由原連結串列可知p=2->next;
          4、然後由反序後的連結串列可知,反序後2->next要指向1,則2->next=1;
          5、好了,現在已經反序一個節點,接著處理下一個節點就需要儲存此時的資訊:
          p1變成剛剛加入的2,即p1=2;p2要變成它的下一節點,就是上面我們儲存的p,即p2=p。

          反序連結串列的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:反序節點 
  4.   (連結串列的頭變成連結串列的尾,連結串列的尾變成頭) 
  5.  返回:指向連結串列表頭的指標 
  6. ========================== 
  7. */  
  8.   
  9. struct student *Reverse (struct student *head)  
  10. {  
  11.     struct student *p;      //臨時儲存  
  12.     struct student *p1;     //儲存返回結果  
  13.     struct student *p2;     //源結果節點一個一個取  
  14.   
  15.     p1 = NULL;          //開始顛倒時,已顛倒的部分為空  
  16.     p2 = head;          //p2指向連結串列的頭節點  
  17.     while(p2 != NULL)  
  18.     {  
  19.         p = p2->next;  
  20.         p2->next = p1;  
  21.         p1 = p2;  
  22.         p2 = p;  
  23.     }  
  24.     head = p1;  
  25.     return head;  
  26. }  

          對連結串列進行選擇排序的基本思想就是反覆從還未排好序的那些節點中,選出鍵值(就是用它排序的欄位,我們取學號num為鍵值)最小的節點,依次重新組合成一個連結串列。

         我認為寫連結串列這類程式,關鍵是理解:head儲存的是第一個節點的地址,head->next儲存的是第二個節點的地址;任意一個節點p的地址,只能通過它前一個節點的next來求得。

        單向連結串列的選擇排序圖示:
         ---->[1]---->[3]---->[2]...---->[n]---->[NULL](原連結串列)
         head   1->next  3->next  2->next   n->next

         ---->[NULL](空連結串列)
        first
        tail

         ---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序後連結串列)
         first   1->next  2->next  3->next   tail->next

         圖10:有N個節點的連結串列選擇排序

        1、先在原連結串列中找最小的,找到一個後就把它放到另一個空的連結串列中;
        2、空連結串列中安放第一個進來的節點,產生一個有序連結串列,並且讓它在原連結串列中分離出來(此時要注意原連結串列中出來的是第一個節點還是中間其它節點);
        3、繼續在原連結串列中找下一個最小的,找到後把它放入有序連結串列的尾指標的next,然後它變成其尾指標;

        對連結串列進行選擇排序的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:選擇排序(由小到大) 
  4.  返回:指向連結串列表頭的指標 
  5. ========================== 
  6. */  
  7. struct student *SelectSort (struct student *head)  
  8. {  
  9.     struct student *first;     //排列後有序鏈的表頭指標  
  10.     struct student *tail;      //排列後有序鏈的表尾指標  
  11.     struct student *p_min;     //保留鍵值更小的節點的前驅節點的指標  
  12.     struct student *min;       //儲存最小節點  
  13.     struct student *p;         //當前比較的節點  
  14.   
  15.     first = NULL;  
  16.     while(head != NULL)       //在連結串列中找鍵值最小的節點  
  17.     {  
  18.         //注意:這裡for語句就是體現選擇排序思想的地方  
  19.         for (p = head, min = head; p->next != NULL; p = p->next)  //迴圈遍歷連結串列中的節點,找出此時最小的節點  
  20.         {  
  21.             if (p->next->num < min->num)     //找到一個比當前min小的節點  
  22.             {  
  23.                 p_min = p;        //儲存找到節點的前驅節點:顯然p->next的前驅節點是p  
  24.                 min = p->next;     //儲存鍵值更小的節點  
  25.             }  
  26.         }  
  27.   
  28.         //上面for語句結束後,就要做兩件事;一是把它放入有序連結串列中;二是根據相應的條件判斷,安排它離開原來的連結串列  
  29.   
  30.         //第一件事  
  31.         if (first == NULL)     //如果有序連結串列目前還是一個空連結串列  
  32.         {  
  33.             first = min;        //第一次找到鍵值最小的節點  
  34.             tail = min;        //注意:尾指標讓它指向最後的一個節點  
  35.         }  
  36.         else              //有序連結串列中已經有節點  
  37.         {  
  38.             tail->next = min;    //把剛找到的最小節點放到最後,即讓尾指標的next指向它  
  39.             tail = min;           //尾指標也要指向它  
  40.         }  
  41.   
  42.         //第二件事  
  43.         if (min == head)            //如果找到的最小節點就是第一個節點  
  44.         {  
  45.             head = head->next;      //顯然讓head指向原head->next,即第二個節點,就OK  
  46.         }  
  47.         else            //如果不是第一個節點  
  48.         {  
  49.             p_min->next = min->next;  //前次最小節點的next指向當前min的next,這樣就讓min離開了原連結串列  
  50.         }  
  51.     }  
  52.   
  53.     if (first != NULL)      //迴圈結束得到有序連結串列first  
  54.     {  
  55.         tail->next = NULL;   //單向連結串列的最後一個節點的next應該指向NULL  
  56.     }  
  57.     head = first;  
  58.     return head;  
  59. }  

           對連結串列進行直接插入排序的基本思想就是假設連結串列的前面n-1個節點是已經按鍵值(就是用它排序的欄位,我們取學號num為鍵值)排好序的,對於節點n在這個序列中找插入位置,使得n插入後新序列仍然有序。按照這種思想,依次對連結串列從頭到尾執行一遍,就可以使無序連結串列變為有序連結串列。

         單向連結串列的直接插入排序圖示:
         ---->[1]---->[3]---->[2]...---->[n]---->[NULL](原連結串列)
        head   1->next  3->next  2->next   n->next

         ---->[1]---->[NULL](從原連結串列中取第1個節點作為只有一個節點的有序連結串列)
        head
        圖11

        ---->[3]---->[2]...---->[n]---->[NULL](原連結串列剩下用於直接插入排序的節點)
        first   3->next  2->next   n->next
        圖12

        ---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序後連結串列)
        head   1->next  2->next  3->next   n->next

        圖13:有N個節點的連結串列直接插入排序

       1、先在原連結串列中以第一個節點為一個有序連結串列,其餘節點為待定節點。
       2、從圖12連結串列中取節點,到圖11連結串列中定位插入。
       3、上面圖示雖說畫了兩條連結串列,其實只有一條連結串列。在排序中,實質只增加了一個用於指向剩下需要排序節點的頭指標first罷了。
       這一點請讀者務必搞清楚,要不然就可能認為它和上面的選擇排序法一樣了。

       對連結串列進行直接插入排序的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:直接插入排序(由小到大) 
  4.  返回:指向連結串列表頭的指標 
  5. ========================== 
  6. */  
  7. struct student *InsertSort (struct student *head)  
  8. {  
  9.     struct student *first;    //為原連結串列剩下用於直接插入排序的節點頭指標  
  10.     struct student *t;        //臨時指標變數:插入節點  
  11.     struct student *p,*q;     //臨時指標變數  
  12.   
  13.     first = head->next;      //原連結串列剩下用於直接插入排序的節點連結串列:可根據圖12來理解  
  14.     head->next = NULL;       //只含有一個節點的連結串列的有序連結串列:可根據圖11來理解  
  15.   
  16.     while(first != NULL)        //遍歷剩下無序的連結串列  
  17.     {  
  18.         //注意:這裡for語句就是體現直接插入排序思想的地方  
  19.         for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);  //無序節點在有序連結串列中找插入的位置  
  20.   
  21.         //退出for迴圈,就是找到了插入的位置,應該將t節點插入到p節點之後,q節點之前  
  22.         //注意:按道理來說,這句話可以放到下面註釋了的那個位置也應該對的,但是就是不能。原因:你若理解了上面的第3條,就知道了  
  23.         //下面的插入就是將t節點即是first節點插入到p節點之後,已經改變了first節點,所以first節點應該在被修改之前往後移動,不能放到下面註釋的位置上去  
  24.         first = first->next; //無序連結串列中的節點離開,以便它插入到有序連結串列中  
  25.   
  26.         if (q == head)      //插在第一個節點之前  
  27.         {  
  28.             head = t;  
  29.         }  
  30.         else            //p是q的前驅  
  31.         {  
  32.             p->next = t;  
  33.         }  
  34.         t->next = q;     //完成插入動作  
  35.         //first = first->next;   
  36.     }  
  37.     return head;  
  38. }  

         對連結串列進行氣泡排序的基本思想就是對當前還未排好序的範圍內的全部節點,自上而下對相鄰的兩個節點依次進行比較和調整,讓鍵值(就是用它排 序的欄位,我們取學號num為鍵值)較大的節點往下沉,鍵值較小的往上冒。即:每當兩相鄰的節點比較後發現它們的排序與排序要求相反時,就將它們互換。

        單向連結串列的氣泡排序圖示:
        ---->[1]---->[3]---->[2]...---->[n]---->[NULL](原連結串列)
       head   1->next  3->next  2->next   n->next

       ---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序後連結串列)
       head   1->next  2->next  3->next   n->next

       圖14:有N個節點的連結串列氣泡排序

      任意兩個相鄰節點p、q位置互換圖示:
      假設p1->next指向p,那麼顯然p1->next->next就指向q,
      p1->next->next->next就指向q的後繼節點,我們用p2儲存
      p1->next->next指標。即:p2=p1->next->next,則有:
       [  ]---->[p]---------->[q]---->[  ](排序前)
       p1->next  p1->next->next  p2->next
       圖15

       [  ]---->[q]---------->[p]---->[  ](排序後)

       圖16

      1、排序後q節點指向p節點,在調整指向之前,我們要儲存原p的指向節點地址,即:p2=p1->next->next;
      2、順著這一步一步往下推,排序後圖16中p1->next->next要指的是p2->next,所以p1->next->next=p2->next;
      3、在圖15中p2->next原是q發出來的指向,排序後圖16中q的指向要變為指向p的,而原來p1->next是指向p的,所以p2->next=p1->next;
      4、在圖15中p1->next原是指向p的,排序後圖16中p1->next要指向q,原來p1->next->next(即p2)是指向q的,所以p1->next=p2;
      5、至此,我們完成了相鄰兩節點的順序交換。
      6、下面的程式描述改進了一點就是記錄了每次最後一次節點下沉的位置,這樣我們不必每次都從頭到尾的掃描,只需要掃描到記錄點為止。 因為後面的都已經是排好序的了。

       對連結串列進行氣泡排序的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:氣泡排序(由小到大) 
  4.  返回:指向連結串列表頭的指標 
  5. ========================== 
  6. */  
  7. struct student *BubbleSort (struct student *head)  
  8. {  
  9.     struct student *endpt;    //控制迴圈比較  
  10.     struct student *p;        //臨時指標變數  
  11.     struct student *p1,*p2;  
  12.   
  13.     p1 = (struct student *) malloc (LEN);  
  14.     p1->next = head;        //注意理解:我們增加一個節點,放在第一個節點的前面,主要是為了便於比較。因為第一個節點沒有前驅,我們不能交換地址  
  15.     head = p1;                 //讓head指向p1節點,排序完成後,我們再把p1節點釋放掉  
  16.   
  17.     for (endpt = NULL; endpt != head; endpt = p)    //結合第6點理解  
  18.     {  
  19.         for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)  
  20.         {  
  21.             if (p1->next->num > p1->next->next->num)  //如果前面的節點鍵值比後面節點的鍵值大,則交換  
  22.             {  
  23.                 p2 = p1->next->next;    //結合第1點理解  
  24.                 p1->next->next = p2->next;   //結合第2點理解  
  25.                 p2->next = p1->next;   //結合第3點理解  
  26.                 p1->next = p2;     //結合第4點理解  
  27.                 p = p1->next->next;   //結合第6點理解  
  28.             }  
  29.         }  
  30.     }  
  31.   
  32.     p1 = head;              //把p1的資訊去掉  
  33.     head = head->next;       //讓head指向排序後的第一個節點  
  34.     free (p1);          //釋放p1  
  35.     p1 = NULL;          //p1置為NULL,保證不產生“野指標”,即地址不確定的指標變數  
  36.   
  37.     return head;  
  38. }  

        有序連結串列插入節點示意圖:

        ---->[NULL](空有序連結串列)
        head

       圖18:空有序連結串列(空有序連結串列好解決,直接讓head指向它就是了。)

       以下討論不為空的有序連結串列。
        ---->[1]---->[2]---->[3]...---->[n]---->[NULL](有序連結串列)
        head   1->next  2->next  3->next   n->next

       圖18:有N個節點的有序連結串列

       插入node節點的位置有兩種情況:一是第一個節點前,二是其它節點前或後。

       ---->[node]---->[1]---->[2]---->[3]...---->[n]---->[NULL]
       head  node->next  1->next  2->next  3->next   n->next

       圖19:node節點插在第一個節點前

       ---->[1]---->[2]---->[3]...---->[node]...---->[n]---->[NULL]
      head   1->next  2->next  3->next    node->next  n->next

       插入有序連結串列的函式為:

[cpp] view plain copy
  1. /* 
  2. ========================== 
  3.  功能:插入有序連結串列的某個節點的後面(從小到大) 
  4.  返回:指向連結串列表頭的指標 
  5. ========================== 
  6. */  
  7.   
  8. struct student *SortInsert (struct student *head, struct student *node)  
  9. {  
  10.     struct student *p;      //p儲存當前需要檢查的節點的地址  
  11.     struct student *t;      //臨時指標變數  
  12.   
  13.     if (head == NULL)       //處理空的有序連結串列  
  14.     {  
  15.         head = node;  
  16.         node->next = NULL;  
  17.         n += 1;         //插入完畢,節點總數加  
  18.         return head;  
  19.     }  
  20.   
  21.     p = head;             //有序連結串列不為空  
  22.     while(p->num < node->num && p != NULL)    //p指向的節點的學號比插入節點的學號小,並且它不等於NULL  
  23.     {  
  24.         t = p;            //儲存當前節點的前驅,以便後面判斷後處理  
  25.         p = p->next;     //後移一個節點  
  26.     }  
  27.   
  28.     if (p == head)      //剛好插入第一個節點之前  
  29.     {  
  30.         node->next = p;  
  31.         head = node;  
  32.     }  
  33.     else                 //插入其它節點之後  
  34.     {  
  35.         t->next = node;      //把node節點加進去  
  36.         node->next = p;  
  37.     }  
  38.     n += 1;         //插入完畢,節點總數加1  
  39.   
  40.     return head;  
  41. }  

綜上所述,連結串列的各類操作函式的完整程式碼如下:

[cpp] view plain copy
  1. #include "stdlib.h"  
  2. #include "stdio.h"  
  3.   
  4. #define NULL 0  
  5. #define LEN sizeof(struct student)  
  6.   
  7. struct student  
  8. {  
  9.     int num;              //學號   
  10.     float score;          //分數,其他資訊可以繼續在下面增加欄位  
  11.     struct student *next;       //指向下一節點的指標  
  12. };  
  13.   
  14. int n;  //節點總數   
  15. /* 
  16. ========================== 
  17. 功能:建立n個節點的連結串列 
  18. 返回:指向連結串列表頭的指標 
  19. ========================== 
  20. */  
  21. struct student *Create()  
  22. {  
  23.     struct student *head;       //頭節點  
  24.     struct student *p1 = NULL;  //p1儲存建立的新節點的地址  
  25.     struct student *p2 = NULL;  //p2儲存原連結串列最後一個節點的地址  
  26.   
  27.     n = 0;          //建立前連結串列的節點總數為0:空連結串列  
  28.     p1 = (struct student *) malloc (LEN);   //開闢一個新節點  
  29.     p2 = p1;            //如果節點開闢成功,則p2先把它的指標儲存下來以備後用  
  30.   
  31.     if(p1==NULL)        //節點開闢不成功  
  32.     {  
  33.         printf ("\nCann't create it, try it again in a moment!\n");  
  34.         return NULL;  
  35.     }  
  36.     else                //節點開闢成功  
  37.     {  
  38.         head = NULL;        //開始head指向NULL  
  39.         printf ("Please input %d node -- num,score: ", n + 1);  
  40.         scanf ("%d %f", &(p1->num), &(p1->score));    //錄入資料  
  41.     }  
  42.     while(p1->num != 0)      //只要學號不為0,就繼續錄入下一個節點  
  43.     {  
  44.         n += 1;         //節點總數增加1個  
  45.         if(n == 1)      //如果節點總數是1,則head指向剛建立的節點p1  
  46.         {  
  47.             head = p1;  
  48.             p2->next = NULL;  //此時的p2就是p1,也就是p1->next指向NULL。  
  49.         }  
  50.         else  
  51.         {  
  52.             p2->next = p1;   //指向上次下面剛剛開闢的新節點  
  53.         }  
  54.   
  55.         p2 = p1;            //把p1的地址給p2保留,然後p1產生新的節點  
  56.   
  57.         p1 = (struct student *) malloc (LEN);  
  58.         printf ("Please input %d node -- num,score: ", n + 1);  
  59.         scanf ("%d %f", &(p1->num), &(p1->score));  
  60.     }  
  61.     p2->next = NULL;     //此句就是根據單向連結串列的最後一個節點要指向NULL  
  62.   
  63.     free(p1);           //p1->num為0的時候跳出了while迴圈,並且釋放p1  
  64.     p1 = NULL;          //特別不要忘記把釋放的變數清空置為NULL,否則就變成"野指標",即地址不確定的指標  
  65.     return head;        //返回建立連結串列的頭指標   
  66. }  
  67.   
  68.   
  69. /* 
  70. =========================== 
  71.  功能:輸出節點 
  72.  返回: void 
  73. =========================== 
  74. */  
  75. void Print(struct student *head)  
  76. {  
  77.     struct student *p;  
  78.     printf ("\nNow , These %d records are:\n", n);  
  79.     p = head;  
  80.     if(head != NULL)        //只要不是空連結串列,就輸出連結串列中所有節點  
  81.     {  
  82.         printf("head is %o\n", head);    //輸出頭指標指向的地址  
  83.         do  
  84.         {  
  85.             /* 
  86.             輸出相應的值:當前節點地址、各欄位值、當前節點的下一節點地址。 
  87.             這樣輸出便於讀者形象看到一個單向連結串列在計算機中的儲存結構,和我們 
  88.             設計的圖示是一模一樣的。 
  89.             */  
  90.             printf ("%o   %d   %5.1f   %o\n", p, p->num, p->score, p->next);  
  91.             p = p->next;     //移到下一個節點  
  92.         }  
  93.         while (p != NULL);  
  94.     }  
  95. }  
  96.   
  97. /* 
  98. ========================== 
  99.  功能:刪除指定節點 
  100.   (此例中是刪除指定學號的節點) 
  101.  返回:指向連結串列表頭的指標 
  102. ========================== 
  103. */  
  104. struct student *Del (struct student *head, int num)  
  105. {  
  106.     struct student *p1;     //p1儲存當前需要檢查的節點的地址  
  107.     struct student *p2;     //p2儲存當前檢查過的節點的地址  
  108.     if (head == NULL)       //是空連結串列(結合圖3理解)  
  109.     {  
  110.         printf ("\nList is null!\n");  
  111.         return head;  
  112.     }  
  113.   
  114.     //定位要刪除的節點  
  115.     p1 = head;  
  116.     while (p1->num != num && p1->next != NULL)    //p1指向的節點不是所要查詢的,並且它不是最後一個節點,就繼續往下找  
  117.     {  
  118.         p2 = p1;            //儲存當前節點的地址  
  119.         p1 = p1->next;       //後移一個節點  
  120.     }  
  121.   
  122.     if(p1->num==num)     //找到了。(結合圖4、5理解)  
  123.     {  
  124.         if (p1 == head)     //如果要刪除的節點是第一個節點  
  125.         {  
  126.             head = p1->next; //頭指標指向第一個節點的後一個節點,也就是第二個節點。這樣第一個節點就不在連結串列中,即刪除  
  127.         }  
  128.         else            //如果是其它節點,則讓原來指向當前節點的指標,指向它的下一個節點,完成刪除  
  129.         {  
  130.             p2->next = p1->next;  
  131.         }  
  132.   
  133.         free (p1);      //釋放當前節點  
  134.         p1 = NULL;  
  135.         printf ("\ndelete %ld success!\n", num);  
  136.         n -= 1;         //節點總數減1個  
  137.     }  
  138.     else                //沒有找到  
  139.     {  
  140.         printf ("\n%ld not been found!\n", num);  
  141.     }  
  142.   
  143.     return head;  
  144. }  
  145.   
  146. //銷燬連結串列  
  147. void DestroyList(struct student *head)  
  148. {  
  149.     struct student *p;  
  150.     if(head==NULL)  
  151.         return 0;  
  152.     while(head)  
  153.     {  
  154.         p=head->next;  
  155.         free(head);  
  156.         head=p;  
  157.     }  
  158.     return 1;  
  159. }  
  160.   
  161. /* 
  162. ========================== 
  163.  功能:插入指定節點的後面 
  164.   (此例中是指定學號的節點) 
  165.  返回:指向連結串列表頭的指標 
  166. ========================== 
  167. */  
  168. struct student *Insert (struct student *head, int num, struct student *node)  
  169. {  
  170.     struct student *p1;     //p1儲存當前需要檢查的節點的地址  
  171.     if (head == NULL)       //(結合圖示7理解)  
  172.     {  
  173.         head = node;  
  174.         node->next = NULL;  
  175.         n += 1;  
  176.         return head;  
  177.     }  
  178.   
  179.     p1 = head;  
  180.     while(p1->num != num && p1->next != NULL)  //p1指向的節點不是所要查詢的,並且它不是最後一個節點,繼續往下找  
  181.     {  
  182.         p1 = p1->next;       //後移一個節點  
  183.     }  
  184.   
  185.     if (p1->num==num)        //找到了(結合圖示8理解)  
  186.     {  
  187.         node->next = p1->next;    //顯然node的下一節點是原p1的next  
  188.         p1->next = node;     //插入後,原p1的下一節點就是要插入的node  
  189.         n += 1;         //節點總數增加1個  
  190.     }  
  191.     else  
  192.     {  
  193.         printf ("\n%ld not been found!\n", num);  
  194.     }  
  195.     return head;  
  196. }  
  197.   
  198. /* 
  199. ========================== 
  200.  功能:反序節點 
  201.   (連結串列的頭變成連結串列的尾,連結串列的尾變成頭) 
  202.  返回:指向連結串列表頭的指標 
  203. ========================== 
  204. */  
  205.   
  206. struct student *Reverse (struct student *head)  
  207. {  
  208.     struct student *p;      //臨時儲存  
  209.     struct student *p1;     //儲存返回結果  
  210.     struct student *p2;     //源結果節點一個一個取  
  211.   
  212.     p1 = NULL;          //開始顛倒時,已顛倒的部分為空  
  213.     p2 = head;          //p2指向連結串列的頭節點  
  214.     while(p2 != NULL)  
  215.     {  
  216.         p = p2->next;  
  217.         p2->next = p1;  
  218.         p1 = p2;  
  219.         p2 = p;  
  220.     }  
  221.     head = p1;  
  222.     return head;  
  223. }  
  224. /* 
  225. ========================== 
  226.  功能:選擇排序(由小到大) 
  227.  返回:指向連結串列表頭的指標 
  228. ========================== 
  229. */  
  230. struct student *SelectSort (struct student *head)  
  231. {  
  232.     struct student *first;     //排列後有序鏈的表頭指標  
  233.     struct student *tail;      //排列後有序鏈的表尾指標  
  234.     struct student *p_min;     //保留鍵值更小的節點的前驅節點的指標  
  235.     struct student *min;       //儲存最小節點  
  236.     struct student *p;         //當前比較的節點  
  237.   
  238.     first = NULL;  
  239.     while(head != NULL)       //在連結串列中找鍵值最小的節點  
  240.     {  
  241.         //注意:這裡for語句就是體現選擇排序思想的地方  
  242.         for (p = head, min = head; p->next != NULL; p = p->next)  //迴圈遍歷連結串列中的節點,找出此時最小的節點  
  243.         {  
  244.             if (p->next->num < min->num)     //找到一個比當前min小的節點  
  245.             {  
  246.                 p_min = p;        //儲存找到節點的前驅節點:顯然p->next的前驅節點是p  
  247.                 min = p->next;     //儲存鍵值更小的節點  
  248.             }  
  249.         }  
  250.   
  251.         //上面for語句結束後,就要做兩件事;一是把它放入有序連結串列中;二是根據相應的條件判斷,安排它離開原來的連結串列  
  252.   
  253.         //第一件事  
  254.         if (first == NULL)     //如果有序連結串列目前還是一個空連結串列  
  255.         {  
  256.             first = min;        //第一次找到鍵值最小的節點  
  257.             tail = min;        //注意:尾指標讓它指向最後的一個節點  
  258.         }  
  259.         else              //有序連結串列中已經有節點  
  260.         {  
  261.             tail->next = min;    //把剛找到的最小節點放到最後,即讓尾指標的next指向它  
  262.             tail = min;           //尾指標也要指向它  
  263.         }  
  264.   
  265.         //第二件事  
  266.         if (min == head)            //如果找到的最小節點就是第一個節點  
  267.         {  
  268.             head = head->next;      //顯然讓head指向原head->next,即第二個節點,就OK  
  269.         }  
  270.         else            //如果不是第一個節點  
  271.         {  
  272.             p_min->next = min->next;  //前次最小節點的next指向當前min的next,這樣就讓min離開了原連結串列  
  273.         }  
  274.     }  
  275.   
  276.     if (first != NULL)      //迴圈結束得到有序連結串列first  
  277.     {  
  278.         tail->next = NULL;   //單向連結串列的最後一個節點的next應該指向NULL  
  279.     }  
  280.     head = first;  
  281.     return head;  
  282. }  
  283.   
  284.   
  285. /* 
  286. ========================== 
  287.  功能:直接插入排序(由小到大) 
  288.  返回:指向連結串列表頭的指標 
  289. ========================== 
  290. */  
  291. struct student *InsertSort (struct student *head)  
  292. {  
  293.     struct student *first;    //為原連結串列剩下用於直接插入排序的節點頭指標  
  294.     struct student *t;        //臨時指標變數:插入節點  
  295.     struct student *p,*q;     //臨時指標變數  
  296.   
  297.     first = head->next;      //原連結串列剩下用於直接插入排序的節點連結串列:可根據圖12來理解  
  298.     head->next = NULL;       //只含有一個節點的連結串列的有序連結串列:可根據圖11來理解  
  299.   
  300.     while(first != NULL)        //遍歷剩下無序的連結串列  
  301.     {  
  302.         //注意:這裡for語句就是體現直接插入排序思想的地方  
  303.         for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);  //無序節點在有序連結串列中找插入的位置  
  304.   
  305.         //退出for迴圈,就是找到了插入的位置,應該將t節點插入到p節點之後,q節點之前  
  306.         //注意:按道理來說,這句話可以放到下面註釋了的那個位置也應該對的,但是就是不能。原因:你若理解了上面的第3條,就知道了  
  307.         //下面的插入就是將t節點即是first節點插入到p節點之後,已經改變了first節點,所以first節點應該在被修改之前往後移動,不能放到下面註釋的位置上去  
  308.         first = first->next; //無序連結串列中的節點離開,以便它插入到有序連結串列中  
  309.   
  310.         if (q == head)      //插在第一個節點之前  
  311.         {  
  312.             head = t;  
  313.         }  
  314.         else            //p是q的前驅  
  315.         {  
  316.             p->next = t;  
  317.         }  
  318.         t->next = q;     //完成插入動作  
  319.         //first = first->next;   
  320.     }  
  321.     return head;  
  322. }  
  323.   
  324. /* 
  325. ========================== 
  326.  功能:氣泡排序(由小到大) 
  327.  返回:指向連結串列表頭的指標 
  328. ========================== 
  329. */  
  330. struct student *BubbleSort (struct student *head)  
  331. {  
  332.     struct student *endpt;    //控制迴圈比較  
  333.     struct student *p;        //臨時指標變數  
  334.     struct student *p1,*p2;  
  335.   
  336.     p1 = (struct student *) malloc (LEN);  
  337.     p1->next = head;        //注意理解:我們增加一個節點,放在第一個節點的前面,主要是為了便於比較。因為第一個節點沒有前驅,我們不能交換地址  
  338.     head = p1;                 //讓head指向p1節點,排序完成後,我們再把p1節點釋放掉  
  339.   
  340.     for (endpt = NULL; endpt != head; endpt = p)    //結合第6點理解  
  341.     {  
  342.         for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)  
  343.         {  
  344.             if (p1->next->num > p1->next->next->num)  //如果前面的節點鍵值比後面節點的鍵值大,則交換  
  345.             {  
  346.                 p2 = p1->next->next;    //結合第1點理解  
  347.                 p1->next->next = p2->next;   //結合第2點理解  
  348.                 p2->next = p1->next;   //結合第3點理解  
  349.                 p1->next = p2;     //結合第4點理解  
  350.                 p = p1->next->next;   //結合第6點理解  
  351.             }  
  352.         }  
  353.     }  
  354.   
  355.     p1 = head;              //把p1的資訊去掉  
  356.     head = head->next;       //讓head指向排序後的第一個節點  
  357.     free (p1);          //釋放p1  
  358.     p1 = NULL;          //p1置為NULL,保證不產生“野指標”,即地址不確定的指標變數  
  359.   
  360.     return head;  
  361. }  
  362.   
  363. /* 
  364. ========================== 
  365.  功能:插入有序連結串列的某個節點的後面(從小到大) 
  366.  返回:指向連結串列表頭的指標 
  367. ========================== 
  368. */  
  369.   
  370. struct student *SortInsert (struct student *head, struct student *node)  
  371. {  
  372.     struct student *p;      //p儲存當前需要檢查的節點的地址  
  373.     struct student *t;      //臨時指標變數  
  374.   
  375.     if (head == NULL)       //處理空的有序連結串列  
  376.     {  
  377.         head = node;  
  378.         node->next = NULL;  
  379.         n += 1;         //插入完畢,節點總數加  
  380.         return head;  
  381.     }  
  382.   
  383.     p = head;             //有序連結串列不為空  
  384.     while(p->num < node->num && p != NULL)    //p指向的節點的學號比插入節點的學號小,並且它不等於NULL  
  385.     {  
  386.         t = p;            //儲存當前節點的前驅,以便後面判斷後處理  
  387.         p = p->next;     //後移一個節點  
  388.     }  
  389.   
  390.     if (p == head)      //剛好插入第一個節點之前  
  391.     {  
  392.         node->next = p;  
  393.         head = node;  
  394.     }  
  395.     else                 //插入其它節點之後  
  396.     {  
  397.         t->next = node;      //把node節點加進去  
  398.         node->next = p;  
  399.     }  
  400.     n += 1;         //插入完畢,節點總數加1  
  401.   
  402.     return head;  
  403. }  
  404.   
  405. /* 
  406. 以上函式的測試程式: 
  407. 提示:根據測試函式的不同註釋相應的程式段,這也是一種測試方法。 
  408. */  
  409. int main(void)  
  410. {  
  411.     struct student *head;  
  412.     struct student *stu;  
  413.     int thenumber;  
  414.   
  415.     // 測試Create()、Print()   
  416.     head = Create();  
  417.     Print(head);  
  418.   
  419.     //測試Del()  
  420.     printf("\nWhich one delete: ");  
  421.     scanf("%d",&thenumber);  
  422.     head = Del(head,thenumber);  
  423.     Print(head);  
  424.   
  425.     //測試Insert()  
  426.     stu = (struct student *)malloc(LEN);  
  427.     printf("\nPlease input insert node -- num,score: ");  
  428.     scanf("%d %f",&stu->num,&stu->score);  
  429.     printf("\nInsert behind num: ");  
  430.     scanf("%d",&thenumber);  
  431.     head = Insert(head,thenumber,stu);  
  432.     Print(head);  
  433.   
  434.     //測試Reverse()  
  435.     printf("\nReverse the LinkList: \n");  
  436.     head = Reverse(head);  
  437.     Print(head);  
  438.   
  439.     //測試SelectSort()  
  440.     printf("\nSelectSort the LinkList: \n");  
  441.     head = SelectSort(head);  
  442.     Print(head);  
  443.   
  444.     //測試InsertSort()  
  445.     printf("\nInsertSort the LinkList: \n");  
  446.     head = InsertSort(head);  
  447.     Print(head);  
  448.   
  449.     //測試BubbleSort()  
  450.     printf("\nBubbleSort the LinkList: \n");  
  451.     head = BubbleSort(head);  
  452.     Print(head);  
  453.   
  454.     printf("\nSortInsert the LinkList: \n");  
  455.     //測試SortInsert():上面建立連結串列,輸入節點時請注意學號num從小到大的順序  
  456.     stu = (struct student *)malloc(LEN);  
  457.     printf("\nPlease input insert node -- num,score: ");  
  458.     scanf("%d %f",&stu->num,&stu->score);  
  459.     head = SortInsert(head,stu);  
  460.     Print(head);  
  461.   
  462.     //銷燬連結串列  
  463.     DestroyList(head);  
  464.   
  465.     printf ("\n");  
  466.     system ("pause");  
  467. }  

相關文章