1. 程式人生 > >連結串列詳細操作

連結串列詳細操作

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

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

  圖1:空連結串列

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

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

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

#include “stdlib.h”
#include “stdio.h”

#define NULL 0
#define LEN sizeof(struct student)

struct student
{
int num; //學號
float score; //分數,其他資訊可以繼續在下面增加欄位
struct student *next; //指向下一節點的指標
};

int n; //節點總數
/*

功能:建立n個節點的連結串列
返回:指向連結串列表頭的指標

*/
struct student *Create()
{
struct student *head; //頭節點
struct student *p1 = NULL; //p1儲存建立的新節點的地址
struct student *p2 = NULL; //p2儲存原連結串列最後一個節點的地址

n = 0;			//建立前連結串列的節點總數為0:空連結串列
p1 = (struct student *) malloc (LEN);	//開闢一個新節點
p2 = p1;			//如果節點開闢成功,則p2先把它的指標儲存下來以備後用

if(p1==NULL)		//節點開闢不成功
{
	printf ("\nCann't create it, try it again in a moment!\n");
	return NULL;
}
else				//節點開闢成功
{
	head = NULL;		//開始head指向NULL
	printf ("Please input %d node -- num,score: ", n + 1);
	scanf ("%d %f", &(p1->num), &(p1->score));	//錄入資料
}
while(p1->num != 0)		//只要學號不為0,就繼續錄入下一個節點
{
	n += 1;			//節點總數增加1個
	if(n == 1)		//如果節點總數是1,則head指向剛建立的節點p1
	{
		head = p1;
		p2->next = NULL;  //此時的p2就是p1,也就是p1->next指向NULL。
	}
	else
	{
		p2->next = p1;	//指向上次下面剛剛開闢的新節點
	}

	p2 = p1;			//把p1的地址給p2保留,然後p1產生新的節點

	p1 = (struct student *) malloc (LEN);
	printf ("Please input %d node -- num,score: ", n + 1);
	scanf ("%d %f", &(p1->num), &(p1->score));
}
p2->next = NULL;		//此句就是根據單向連結串列的最後一個節點要指向NULL

free(p1);			//p1->num為0的時候跳出了while迴圈,並且釋放p1
p1 = NULL;			//特別不要忘記把釋放的變數清空置為NULL,否則就變成"野指標",即地址不確定的指標
return head;	    //返回建立連結串列的頭指標 

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

/*

功能:輸出節點
返回: void

*/
void Print(struct student *head)
{
struct student p;
printf ("\nNow , These %d records are:\n", n);
p = head;
if(head != NULL) //只要不是空連結串列,就輸出連結串列中所有節點
{
printf(“head is %o\n”, head); //輸出頭指標指向的地址
do
{
/

輸出相應的值:當前節點地址、各欄位值、當前節點的下一節點地址。
這樣輸出便於讀者形象看到一個單向連結串列在計算機中的儲存結構,和我們
設計的圖示是一模一樣的。
*/
printf ("%o %d %5.1f %o\n", p, p->num, p->score, p->next);
p = p->next; //移到下一個節點
}
while (p != NULL);
}
}
單向連結串列的刪除圖示:
---->[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。

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

/*

功能:刪除指定節點
(此例中是刪除指定學號的節點)
返回:指向連結串列表頭的指標

*/
struct student *Del (struct student *head, int num)
{
struct student *p1; //p1儲存當前需要檢查的節點的地址
struct student *p2; //p2儲存當前檢查過的節點的地址
if (head == NULL) //是空連結串列(結合圖3理解)
{
printf ("\nList is null!\n");
return head;
}

//定位要刪除的節點
p1 = head;
while (p1->num != num && p1->next != NULL)	//p1指向的節點不是所要查詢的,並且它不是最後一個節點,就繼續往下找
{
	p2 = p1;			//儲存當前節點的地址
	p1 = p1->next;		//後移一個節點
}

if(p1->num==num)		//找到了。(結合圖4、5理解)
{
	if (p1 == head)		//如果要刪除的節點是第一個節點
	{
		head = p1->next;	//頭指標指向第一個節點的後一個節點,也就是第二個節點。這樣第一個節點就不在連結串列中,即刪除
	}
	else			//如果是其它節點,則讓原來指向當前節點的指標,指向它的下一個節點,完成刪除
	{
		p2->next = p1->next;
	}

	free (p1);		//釋放當前節點
	p1 = NULL;
	printf ("\ndelete %ld success!\n", num);
	n -= 1;			//節點總數減1個
}
else				//沒有找到
{
	printf ("\n%ld not been found!\n", num);
}

return head;

}
單向連結串列的插入圖示:
---->[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。

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

/*

功能:插入指定節點的後面
(此例中是指定學號的節點)
返回:指向連結串列表頭的指標

*/
struct student *Insert (struct student *head, int num, struct student *node)
{
struct student *p1; //p1儲存當前需要檢查的節點的地址
if (head == NULL) //(結合圖示7理解)
{
head = node;
node->next = NULL;
n += 1;
return head;
}

p1 = head;
while(p1->num != num && p1->next != NULL)	 //p1指向的節點不是所要查詢的,並且它不是最後一個節點,繼續往下找
{
	p1 = p1->next;		//後移一個節點
}

if (p1->num==num)		//找到了(結合圖示8理解)
{
	node->next = p1->next;	//顯然node的下一節點是原p1的next
	p1->next = node;		//插入後,原p1的下一節點就是要插入的node
	n += 1;			//節點總數增加1個
}
else
{
	printf ("\n%ld not been found!\n", num);
}
return head;

}
單向連結串列的反序圖示:
---->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。

      反序連結串列的函式為:

/*

功能:反序節點
(連結串列的頭變成連結串列的尾,連結串列的尾變成頭)
返回:指向連結串列表頭的指標

*/

struct student *Reverse (struct student *head)
{
struct student *p; //臨時儲存
struct student *p1; //儲存返回結果
struct student *p2; //源結果節點一個一個取

p1 = NULL;			//開始顛倒時,已顛倒的部分為空
p2 = head;			//p2指向連結串列的頭節點
while(p2 != NULL)
{
	p = p2->next;
	p2->next = p1;
	p1 = p2;
	p2 = p;
}
head = p1;
return head;

}
對連結串列進行選擇排序的基本思想就是反覆從還未排好序的那些節點中,選出鍵值(就是用它排序的欄位,我們取學號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,然後它變成其尾指標;

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

/*

功能:選擇排序(由小到大)
返回:指向連結串列表頭的指標

*/
struct student *SelectSort (struct student *head)
{
struct student *first; //排列後有序鏈的表頭指標
struct student *tail; //排列後有序鏈的表尾指標
struct student *p_min; //保留鍵值更小的節點的前驅節點的指標
struct student *min; //儲存最小節點
struct student *p; //當前比較的節點

first = NULL;
while(head != NULL)		  //在連結串列中找鍵值最小的節點
{
	//注意:這裡for語句就是體現選擇排序思想的地方
	for (p = head, min = head; p->next != NULL; p = p->next)	//迴圈遍歷連結串列中的節點,找出此時最小的節點
	{
		if (p->next->num < min->num)	 //找到一個比當前min小的節點
		{
			p_min = p;	      //儲存找到節點的前驅節點:顯然p->next的前驅節點是p
			min = p->next;	  //儲存鍵值更小的節點
		}
	}

	//上面for語句結束後,就要做兩件事;一是把它放入有序連結串列中;二是根據相應的條件判斷,安排它離開原來的連結串列

	//第一件事
	if (first == NULL)	   //如果有序連結串列目前還是一個空連結串列
	{
		first = min;		//第一次找到鍵值最小的節點
		tail = min;		   //注意:尾指標讓它指向最後的一個節點
	}
	else			  //有序連結串列中已經有節點
	{
		tail->next = min;   	//把剛找到的最小節點放到最後,即讓尾指標的next指向它
		tail = min;		      //尾指標也要指向它
	}

	//第二件事
	if (min == head)		    //如果找到的最小節點就是第一個節點
	{
		head = head->next;	   //顯然讓head指向原head->next,即第二個節點,就OK
	}
	else			//如果不是第一個節點
	{
		p_min->next = min->next;	//前次最小節點的next指向當前min的next,這樣就讓min離開了原連結串列
	}
}

if (first != NULL)		//迴圈結束得到有序連結串列first
{
	tail->next = NULL;	//單向連結串列的最後一個節點的next應該指向NULL
}
head = first;
return head;

}
對連結串列進行直接插入排序的基本思想就是假設連結串列的前面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罷了。
   這一點請讀者務必搞清楚,要不然就可能認為它和上面的選擇排序法一樣了。

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

/*

功能:直接插入排序(由小到大)
返回:指向連結串列表頭的指標

*/
struct student *InsertSort (struct student *head)
{
struct student *first; //為原連結串列剩下用於直接插入排序的節點頭指標
struct student *t; //臨時指標變數:插入節點
struct student *p,*q; //臨時指標變數

first = head->next;		//原連結串列剩下用於直接插入排序的節點連結串列:可根據圖12來理解
head->next = NULL;		//只含有一個節點的連結串列的有序連結串列:可根據圖11來理解

while(first != NULL)		//遍歷剩下無序的連結串列
{
	//注意:這裡for語句就是體現直接插入排序思想的地方
	for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);	//無序節點在有序連結串列中找插入的位置

	//退出for迴圈,就是找到了插入的位置,應該將t節點插入到p節點之後,q節點之前
	//注意:按道理來說,這句話可以放到下面註釋了的那個位置也應該對的,但是就是不能。原因:你若理解了上面的第3條,就知道了
	//下面的插入就是將t節點即是first節點插入到p節點之後,已經改變了first節點,所以first節點應該在被修改之前往後移動,不能放到下面註釋的位置上去
	first = first->next;	//無序連結串列中的節點離開,以便它插入到有序連結串列中

	if (q == head)		//插在第一個節點之前
	{
		head = t;
	}
	else			//p是q的前驅
	{
		p->next = t;
	}
	t->next = q;		//完成插入動作
	//first = first->next; 
}
return head;

}
對連結串列進行氣泡排序的基本思想就是對當前還未排好序的範圍內的全部節點,自上而下對相鄰的兩個節點依次進行比較和調整,讓鍵值(就是用它排 序的欄位,我們取學號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、下面的程式描述改進了一點就是記錄了每次最後一次節點下沉的位置,這樣我們不必每次都從頭到尾的掃描,只需要掃描到記錄點為止。 因為後面的都已經是排好序的了。

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

/*

功能:氣泡排序(由小到大)
返回:指向連結串列表頭的指標

*/
struct student *BubbleSort (struct student *head)
{
struct student *endpt; //控制迴圈比較
struct student *p; //臨時指標變數
struct student *p1,*p2;

p1 = (struct student *) malloc (LEN);
p1->next = head;		   //注意理解:我們增加一個節點,放在第一個節點的前面,主要是為了便於比較。因為第一個節點沒有前驅,我們不能交換地址
head = p1;			       //讓head指向p1節點,排序完成後,我們再把p1節點釋放掉

for (endpt = NULL; endpt != head; endpt = p)	//結合第6點理解
{
	for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)
	{
		if (p1->next->num > p1->next->next->num)	//如果前面的節點鍵值比後面節點的鍵值大,則交換
		{
			p2 = p1->next->next;	  //結合第1點理解
			p1->next->next = p2->next;	  //結合第2點理解
			p2->next = p1->next;	 //結合第3點理解
			p1->next = p2;	  //結合第4點理解
			p = p1->next->next;	//結合第6點理解
		}
	}
}

p1 = head;			    //把p1的資訊去掉
head = head->next;		//讓head指向排序後的第一個節點
free (p1);			//釋放p1
p1 = NULL;			//p1置為NULL,保證不產生“野指標”,即地址不確定的指標變數

return head;

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

    ---->[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

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

/*

功能:插入有序連結串列的某個節點的後面(從小到大)
返回:指向連結串列表頭的指標

*/

struct student *SortInsert (struct student *head, struct student *node)
{
struct student *p; //p儲存當前需要檢查的節點的地址
struct student *t; //臨時指標變數

if (head == NULL)		//處理空的有序連結串列
{
	head = node;
	node->next = NULL;
	n += 1;			//插入完畢,節點總數加
	return head;
}

p = head;			  //有序連結串列不為空
while(p->num < node->num && p != NULL)	   //p指向的節點的學號比插入節點的學號小,並且它不等於NULL
{
	t = p;			  //儲存當前節點的前驅,以便後面判斷後處理
	p = p->next;		//後移一個節點
}

if (p == head)		//剛好插入第一個節點之前
{
	node->next = p;
	head = node;
}
else				 //插入其它節點之後
{
	t->next = node;		//把node節點加進去
	node->next = p;
}
n += 1;			//插入完畢,節點總數加1

return head;

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

#include “stdlib.h”
#include “stdio.h”

#define NULL 0
#define LEN sizeof(struct student)

struct student
{
int num; //學號
float score; //分數,其他資訊可以繼續在下面增加欄位
struct student *next; //指向下一節點的指標
};

int n; //節點總數
/*

功能:建立n個節點的連結串列
返回:指向連結串列表頭的指標

*/
struct student *Create()
{
struct student *head; //頭節點
struct student *p1 = NULL; //p1儲存建立的新節點的地址
struct student *p2 = NULL; //p2儲存原連結串列最後一個節點的地址

n = 0;			//建立前連結串列的節點總數為0:空連結串列
p1 = (struct student *) malloc (LEN);	//開闢一個新節點
p2 = p1;			//如果節點開闢成功,則p2先把它的指標儲存下來以備後用

if(p1==NULL)		//節點開闢不成功
{
	printf ("\nCann't create it, try it again in a moment!\n");
	return NULL;
}
else				//節點開闢成功
{
	head = NULL;		//開始head指向NULL
	printf ("Please input %d node -- num,score: ", n + 1);
	scanf ("%d %f", &(p1->num), &(p1->score));	//錄入資料
}
while(p1->num != 0)		//只要學號不為0,就繼續錄入下一個節點
{
	n += 1;			//節點總數增加1個
	if(n == 1)		//如果節點總數是1,則head指向剛建立的節點p1
	{
		head = p1;
		p2->next = NULL;  //此時的p2就是p1,也就是p1->next指向NULL。
	}
	else
	{
		p2->next = p1;	//指向上次下面剛剛開闢的新節點
	}

	p2 = p1;			//把p1的地址給p2保留,然後p1產生新的節點

	p1 = (struct student *) malloc (LEN);
	printf ("Please input %d node -- num,score: ", n + 1);
	scanf ("%d %f", &(p1->num), &(p1->score));
}
p2->next = NULL;		//此句就是根據單向連結串列的最後一個節點要指向NULL

free(p1);			//p1->num為0的時候跳出了while迴圈,並且釋放p1
p1 = NULL;			//特別不要忘記把釋放的變數清空置為NULL,否則就變成"野指標",即地址不確定的指標
return head;	    //返回建立連結串列的頭指標 

}

/*

功能:輸出節點
返回: void

*/
void Print(struct student *head)
{
struct student p;
printf ("\nNow , These %d records are:\n", n);
p = head;
if(head != NULL) //只要不是空連結串列,就輸出連結串列中所有節點
{
printf(“head is %o\n”, head); //輸出頭指標指向的地址
do
{
/

輸出相應的值:當前節點地址、各欄位值、當前節點的下一節點地址。
這樣輸出便於讀者形象看到一個單向連結串列在計算機中的儲存結構,和我們
設計的圖示是一模一樣的。
*/
printf ("%o %d %5.1f %o\n", p, p->num, p->score, p->next);
p = p->next; //移到下一個節點
}
while (p != NULL);
}
}

/*

功能:刪除指定節點
(此例中是刪除指定學號的節點)
返回:指向連結串列表頭的指標

*/
struct student *Del (struct student *head, int num)
{
struct student *p1; //p1儲存當前需要檢查的節點的地址
struct student *p2; //p2儲存當前檢查過的節點的地址
if (head == NULL) //是空連結串列(結合圖3理解)
{
printf ("\nList is null!\n");
return head;
}

//定位要刪除的節點
p1 = head;
while (p1->num != num && p1->next != NULL)	//p1指向的節點不是所要查詢的,並且它不是最後一個節點,就繼續往下找
{
	p2 = p1;			//儲存當前節點的地址
	p1 = p1->next;		//後移一個節點
}

if(p1->num==num)		//找到了。(結合圖4、5理解)
{
	if (p1 == head)		//如果要刪除的節點是第一個節點
	{
		head = p1->next;	//頭指標指向第一個節點的後一個節點,也就是第二個節點。這樣第一個節點就不在連結串列中,即刪除
	}
	else			//如果是其它節點,則讓原來指向當前節點的指標,指向它的下一個節點,完成刪除
	{
		p2->next = p1->next;
	}

	free (p1);		//釋放當前節點
	p1 = NULL;
	printf ("\ndelete %ld success!\n", num);
	n -= 1;			//節點總數減1個
}
else				//沒有找到
{
	printf ("\n%ld not been found!\n", num);
}

return head;

}

//銷燬連結串列
void DestroyList(struct student *head)
{
struct student *p;
if(head==NULL)
return 0;
while(head)
{
p=head->next;
free(head);
head=p;
}
return 1;
}

/*

功能:插入指定節點的後面
(此例中是指定學號的節點)
返回:指向連結串列表頭的指標

*/
struct student *Insert (struct student *head, int num, struct student *node)
{
struct student *p1; //p1儲存當前需要檢查的節點的地址
if (head == NULL) //(結合圖示7理解)
{
head = node;
node->next = NULL;
n += 1;
return head;
}

p1 = head;
while(p1->num != num && p1->next != NULL)	 //p1指向的節點不是所要查詢的,並且它不是最後一個節點,繼續往下找
{
	p1 = p1->next;		//後移一個節點
}

if (p1->num==num)		//找到了(結合圖示8理解)
{
	node->next = p1->next;	//顯然node的下一節點是原p1的next
	p1->next = node;		//插入後,原p1的下一節點就是要插入的node
	n += 1;			//節點總數增加1個
}
else
{
	printf ("\n%ld not been found!\n", num);
}
return head;

}

/*

功能:反序節點
(連結串列的頭變成連結串列的尾,連結串列的尾變成頭)
返回:指向連結串列表頭的指標

*/

struct student *Reverse (struct student *head)
{
struct student *p; //臨時儲存
struct student *p1; //儲存返回結果
struct student *p2; //源結果節點一個一個取

p1 = NULL;			//開始顛倒時,已顛倒的部分為空
p2 = head;			//p2指向連結串列的頭節點
while(p2 != NULL)
{
	p = p2->next;
	p2->next = p1;
	p1 = p2;
	p2 = p;
}
head = p1;
return head;

}
/*

功能:選擇排序(由小到大)
返回:指向連結串列表頭的指標

*/
struct student *SelectSort (struct student *head)
{
struct student *first; //排列後有序鏈的表頭指標
struct student *tail; //排列後有序鏈的表尾指標
struct student *p_min; //保留鍵值更小的節點的前驅節點的指標
struct student *min; //儲存最小節點
struct student *p; //當前比較的節點

first = NULL;
while(head != NULL)		  //在連結串列中找鍵值最小的節點
{
	//注意:這裡for語句就是體現選擇排序思想的地方
	for (p = head, min = head; p->next != NULL; p = p->next)	//迴圈遍歷連結串列中的節點,找出此時最小的節點
	{
		if (p->next->num < min->num)	 //找到一個比當前min小的節點
		{
			p_min = p;	      //儲存找到節點的前驅節點:顯然p->next的前驅節點是p
			min = p->next;	  //儲存鍵值更小的節點
		}
	}

	//上面for語句結束後,就要做兩件事;一是把它放入有序連結串列中;二是根據相應的條件判斷,安排它離開原來的連結串列

	//第一件事
	if (first == NULL)	   //如果有序連結串列目前還是一個空連結串列
	{
		first = min;		//第一次找到鍵值最小的節點
		tail = min;		   //注意:尾指標讓它指向最後的一個節點
	}
	else			  //有序連結串列中已經有節點
	{
		tail->next = min;   	//把剛找到的最小節點放到最後,即讓尾指標的next指向它
		tail = min;		      //尾指標也要指向它
	}

	//第二件事
	if (min == head)		    //如果找到的最小節點就是第一個節點
	{
		head = head->next;	   //顯然讓head指向原head->next,即第二個節點,就OK
	}
	else			//如果不是第一個節點
	{
		p_min->next = min->next;	//前次最小節點的next指向當前min的next,這樣就讓min離開了原連結串列
	}
}

if (first != NULL)		//迴圈結束得到有序連結串列first
{
	tail->next = NULL;	//單向連結串列的最後一個節點的next應該指向NULL
}
head = first;
return head;

}

/*

功能:直接插入排序(由小到大)
返回:指向連結串列表頭的指標

*/
struct student *InsertSort (struct student *head)
{
struct student *first; //為原連結串列剩下用於直接插入排序的節點頭指標
struct student *t; //臨時指標變數:插入節點
struct student *p,*q; //臨時指標變數

first = head->next;		//原連結串列剩下用於直接插入排序的節點連結串列:可根據圖12來理解
head->next = NULL;		//只含有一個節點的連結串列的有序連結串列:可根據圖11來理解

while(first != NULL)		//遍歷剩下無序的連結串列
{
	//注意:這裡for語句就是體現直接插入排序思想的地方
	for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);	//無序節點在有序連結串列中找插入的位置

	//退出for迴圈,就是找到了插入的位置,應該將t節點插入到p節點之後,q節點之前
	//注意:按道理來說,這句話可以放到下面註釋了的那個位置也應該對的,但是就是不能。原因:你若理解了上面的第3條,就知道了
	//下面的插入就是將t節點即是first節點插入到p節點之後,已經改變了first節點,所以first節點應該在被修改之前往後移動,不能放到下面註釋的位置上去
	first = first->next;	//無序連結串列中的節點離開,以便它插入到有序連結串列中

	if (q == head)		//插在第一個節點之前
	{
		head = t;
	}
	else			//p是q的前驅
	{
		p->next = t;
	}
	t->next = q;		//完成插入動作
	//first = first->next; 
}
return head;

}

/*

功能:氣泡排序(由小到大)
返回:指向連結串列表頭的指標

*/
struct student *BubbleSort (struct student *head)
{
struct student *endpt; //控制迴圈比較
struct student *p; //臨時指標變數
struct student *p1,*p2;

p1 = (struct student *) malloc (LEN);
p1->next = head;		   //注意理解:我們增加一個節點,放在第一個節點的前面,主要是為了便於比較。因為第一個節點沒有前驅,我們不能交換地址
head = p1;			       //讓head指向p1節點,排序完成後,我們再把p1節點釋放掉

for (endpt = NULL; endpt != head; endpt = p)	//結合第6點理解
{
	for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)
	{
		if (p1->next->num > p1->next->next->num)	//如果前面的節點鍵值比後面節點的鍵值大,則交換
		{
			p2 = p1->next->next;	  //結合第1點理解
			p1->next->next = p2->next;	  //結合第2點理解
			p2->next = p1->next;	 //結合第3點理解
			p1->next = p2;	  //結合第4點理解
			p = p1->next->next;	//結合第6點理解
		}
	}
}

p1 = head;			    //把p1的資訊去掉
head = head->next;		//讓head指向排序後的第一個節點
free (p1);			//釋放p1
p1 = NULL;			//p1置為NULL,保證不產生“野指標”,即地址不確定的指標變數

return head;

}

/*

功能:插入有序連結串列的某個節點的後面(從小到大)
返回:指向連結串列表頭的指標

*/

struct student *SortInsert (struct student *head, struct student *node)
{
struct student *p; //p儲存當前需要檢查的節點的地址
struct student *t; //臨時指標變數

if (head == NULL)		//處理空的有序連結串列
{
	head = node;
	node->next = NULL;
	n += 1;			//插入完畢,節點總數加
	return head;
}

p = head;			  //有序連結串列不為空
while(p->num < node->num && p != NULL)	   //p指向的節點的學號比插入節點的學號小,並且它不等於NULL
{
	t = p;			  //儲存當前節點的前驅,以便後面判斷後處理
	p = p->next;		//後移一個節點
}

if (p == head)		//剛好插入第一個節點之前
{
	node->next = p;
	head = node;
}
else				 //插入其它節點之後
{
	t->next = node;		//把node節點加進去
	node->next = p;
}
n += 1;			//插入完畢,節點總數加1

return head;

}

/*
以上函式的測試程式:
提示:根據測試函式的不同註釋相應的程式段,這也是一種測試方法。
*/
int main(void)
{
struct student *head;
struct student *stu;
int thenumber;

// 測試Create()、Print() 
head = Create();
Print(head);

//測試Del()
printf("\nWhich one delete: ");
scanf("%d",&thenumber);
head = Del(head,thenumber);
Print(head);

//測試Insert()
stu = (struct student *)malloc(LEN);
printf("\nPlease input insert node -- num,score: ");
scanf("%d %f",&stu->num,&stu->score);
printf("\nInsert behind num: ");
scanf("%d",&thenumber);
head = Insert(head,thenumber,stu);
Print(head);

//測試Reverse()
printf("\nReverse the LinkList: \n");
head = Reverse(head);
Print(head);

//測試SelectSort()
printf("\nSelectSort the LinkList: \n");
head = SelectSort(head);
Print(head);

//測試InsertSort()
printf("\nInsertSort the LinkList: \n");
head = InsertSort(head);
Print(head);

//測試BubbleSort()
printf("\nBubbleSort the LinkList: \n");
head = BubbleSort(head);
Print(head);

printf("\nSortInsert the LinkList: \n");
//測試SortInsert():上面建立連結串列,輸入節點時請注意學號num從小到大的順序
stu = (struct student *)malloc(LEN);
printf("\nPlease input insert node -- num,score: ");
scanf("%d %f",&stu->num,&stu->score);
head = SortInsert(head,stu);
Print(head);

//銷燬連結串列
DestroyList(head);

printf ("\n");
system ("pause");

}


作者:hackbuteer1
來源:CSDN
原文:https://blog.csdn.net/Hackbuteer1/article/details/6591486
版權宣告:本文為博主原創文章,轉載請附上博文連結!

歡迎使用Markdown編輯器

你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,瞭解一下Markdown的基本語法知識。

新的改變

我們對Markdown編輯器進行了一些功能拓展與語法支援,除了標準的Markdown編輯器功能,我們增加了如下幾點新功能,幫助你用它寫部落格:

  1. 全新的介面設計 ,將會帶來全新的寫作體驗;
  2. 在創作中心設定你喜愛的程式碼高亮樣式,Markdown 將程式碼片顯示選擇的高亮樣式 進行展示;
  3. 增加了 圖片拖拽 功能,你可以將本地的圖片直接拖拽到編輯區域直接展示;
  4. 全新的 KaTeX數學公式 語法;
  5. 增加了支援甘特圖的mermaid語法1 功能;
  6. 增加了 多螢幕編輯 Markdown文章功能;
  7. 增加了 焦點寫作模式、預覽模式、簡潔寫作模式、左右區域同步滾輪設定 等功能,功能按鈕位於編輯區域與預覽區域中間;
  8. 增加了 檢查列表 功能。

功能快捷鍵

撤銷:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜體:Ctrl/Command + I
標題:Ctrl/Command + Shift + H
無序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
檢查列表:Ctrl/Command + Shift + C
插入程式碼:Ctrl/Command + Shift + K
插入連結:Ctrl/Command + Shift + L
插入圖片:Ctrl/Command + Shift + G

合理的建立標題,有助於目錄的生成

直接輸入1次#,並按下space後,將生成1級標題。
輸入2次#,並按下space後,將生成2級標題。
以此類推,我們支援6級標題。有助於使用TOC語法後生成一個完美的目錄。

如何改變文字的樣式

強調文字 強調文字

加粗文字 加粗文字

標記文字

刪除文字

引用文字

H2O is是液體。

210 運算結果是 1024.

插入連結與圖片

連結: link.

圖片: Alt

帶尺寸的圖片: Alt

當然,我們為了讓使用者更加便捷,我們增加了圖片拖拽功能。

如何插入一段漂亮的程式碼片

部落格設定頁面,選擇一款你喜歡的程式碼片高亮樣式,下面展示同樣高亮的 程式碼片.

// An highlighted block
var foo = 'bar';

生成一個適合你的列表

  • 專案
    • 專案
      • 專案
  1. 專案1
  2. 專案2
  3. 專案3
  • 計劃任務
  • 完成任務

建立一個表格

一個簡單的表格是這麼建立的:

專案 Value
電腦 $1600
手機 $12
導管 $1

設定內容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文字居中 第二列文字居右 第三列文字居左

SmartyPants

SmartyPants將ASCII標點字元轉換為“智慧”印刷標點HTML實體。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

建立一個自定義列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何建立一個註腳

一個具有註腳的文字。2

註釋也是必不可少的

Markdown將文字轉換為 HTML

KaTeX數學公式

您可以使用渲染LaTeX數學表示式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n 1 ) ! n N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 是通過尤拉積分

Γ ( z ) = 0 t z 1 e t d t &ThinSpace; . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

你可以找到更多關於的資訊 LaTeX 數學表示式here.

新的甘特圖功能,豐富你的文章

  • 關於 甘特圖 語法,參考 這兒,

UML 圖表

可以使用UML圖表進行渲染。 Mermaid. 例如下面產生的一個序列圖::

這將產生一個流程圖。:

  • 關於 Mermaid 語法,參考 這兒,

FLowchart流程圖

我們依舊會支援flowchart的流程圖:

  • 關於 Flowchart流程圖 語法,參考 這兒.

匯出與匯入

匯出

如果你想嘗試使用此編輯器, 你可以在此篇文章任意編輯。當你完成了一篇文章的寫作, 在上方工具欄找到 文章匯出 ,生成一個.md檔案或者.html檔案進行本地儲存。

匯入

如果你想載入一篇你寫過的.md檔案或者.html檔案,在上方工具欄可以選擇匯入功能進行對應副檔名的檔案匯入,
繼續你的創作。


  1. mermaid語法說明 ↩︎

  2. 註腳的解釋 ↩︎