1. 程式人生 > >資料結構與演算法之五 連結列表

資料結構與演算法之五 連結列表

視訊課堂https://edu.csdn.net/course/play/7621
在本章中,你將學習: 認識連結列表的特性 執行單鏈接列表
假定您已經編寫了一個演算法來 產生並存儲 1 10,00,000 之間的所有質數,然 後顯示它們。 您如何解決這個問題? 考慮以下使用陣列來解決此問題的演算法: 1. Set I = 0 2. Repeat step 3 varying N from 2 to 1000000
3. If N is a prime number a. Set A[I] = N  b. I = I + 1 4. Repeat step 5 varying J from 0 to I-1 5. Display A[J] 

此演算法有什麼問題? 1 10,00,000 之間的質數個數還不知道。如果使用陣列來儲存這些質數,您不
得不武斷地宣告一個相當大的陣列來儲存這些質數。 此方法的缺點,假定您宣告陣列大小為 N 如果 1 10,00,000 之間的質數個數多於 N ,就不能儲存所有的質數。 如果質數個數比 N 小得多,則就浪費了許多空間。

因此,如果您預先不知道元素的總數您就不能使用陣列來儲存這些數。 那你該怎麼做? 可以使用動態資料結構,它不需要預先指定大小,當需要記憶體時可以隨時進行分
配。
在你宣告一個數組時分配連續的記憶體塊。 讓我們假定宣告一個大小為 10 的陣列以儲存前 10 個質數。
如果您知道陣列的第一個元素的地址,就可以容易地計算出其他元素的地址。 如下所示: 第一個元素的地址 + ( 元素的大小 × 元素索引 )

動態分配記憶體時,可以從記憶體的任何位置分配記憶體塊。 因此,與陣列不同,這些塊可能會不連續,它們可能會隨機分佈在記憶體中。
要訪問任何元素,需要知道其地址。
現在,如果您知道第一個元素的地址,也無法計算出其他元素的地址。 這是因為所有的元素都是隨機儲存在記憶體中。
因此,使每個分配的記憶體塊能夠按序保持下一個塊的地址是十分必要的。 這樣為記憶體塊給定了一個連結結構,每個塊都連結到序列中的下一塊。
實現此概念的資料結構的示例就是連結列表。
你可以宣告一個變數 START ,它儲存第一個塊的地址。 你現在可以從 START 開始並通過下面的連結穿過列表。
連結列表: 是一個動態資料結構。 在需要時允許你分配記憶體。 由元素鏈組成,在此元素鏈中每一個元素都被指定為一個節點。

節點是連結列表的基本構建塊。 一個節點由兩部分組成: 資料: 指的是節點儲存的資訊。 連結: 儲存列表中下一個節點的地址。

連結列表中的最後一個節點不指向任何其它節點。因此,它指向 NULL

連結列表中的所有節點是顯示在記憶體空間的任意位置的。 因此,連結列表中的每一個節點都有儲存序列中下一個節點地址的連結欄位。

要了解列表的第一個節點,宣告一個變數 / 指示字 START ,它總是指向第一 個節點。 當列表為空時, START 包含 NULL

讓我們使用連結列表解決給定的儲存質數的問題。 1. 重複步驟 2 N 介於 2 1000000 2. 如果 N 是一個質數,將它插入到連結列表中。 a. 為新節點分配記憶體。 b. 在新節點中儲存此質數。 c. 在連結列表中附加新節點。 3. 顯示儲存在連結列表中的質數。

考慮給出的演算法以在連結表中插入節點。

1. 為新節點分配記憶體。 2. 2. 為新節點的資料欄位分配值。 3. 3. 如果 START NULL ,則: a. 使 START 指向新的節點。 b. 轉至步驟 6 4. 4. 找到列表中最後一個節點,將它標記為 currentNode 。要找到列表中的最後一個節點,請執行以下步驟: a. 使第一個節點成為 currentNode b. 重複步驟 c ,直到   currentNode successor 變成 NULL c. 使 currentNode 指向序列中的下一個節點。 5. 使 currentNode next 欄位指向新節點。 6. 6. 使新節點的 next 欄位指向 NULL

為遍歷連結表運用演算法。

1. 使 currentNode 指向列表中的第一個節點。 2. 2. 重複步驟 3 和步驟 4 ,直到 currentNode 成為 NULL 為止。 3. 3. 顯示標記為 currentNode 的節點中包含的資訊。 4. 4. 使 currentNode 指向序列中的下一個節點。

編寫一演算法以在連結列表中刪除第一個節點。

1. 使列表中的第一個節點成為當前節點。 2. 2. 使 START 指向序列中的下一個節點。 3. 3. 釋放標記為當前節點的記憶體。

編寫一演算法以刪除連結列表中兩個節點之間的節點。

1. 找到要刪除的節點,將要刪除的節點標記為當前節點,將它前一個節點標記為前一個節點。要找到當前節點和前一個節點,請 執行以下步驟: a. 將前一個節點設定為 START b. 將當前節點設定為 START c. 重複步驟 d e ,直到找到該節點或當前節點變成 NULL 為止。 d. 使前一個節點指向當前節點。 e. 使當前節點指向序列中的下一個節點。 2. 2. 使前一個節點的 next 欄位指向當前節點的後繼者。 3. 3. 釋放標記為當前節點的節點記憶體。

通過定義兩個類,在程式中表示連結列表: Node 類: 節點是連結列表中的基礎構建塊。 Node 類代表連結列表中的節點。 它包括各種資料型別的資料成員,代表要在連結列表中儲存的資料,它還包含對 類型別( node )的引用以保持對序列中下一個節點的引用。 // C# 程式碼 class Node {    public int data;      public Node next;   // 包含序列中下一個                        // 節點地址的變數 }


// C++ 程式碼 class Node {    public:     int data;     Node *next;   // 指向序列中下一個節點  };
List 類: 該類由一組執行連結列表的操作組成。這些操作是插入、刪除、搜尋和 遍歷。它還包含變數 / 指標 START 的宣告, START 始終指向列表中的第一個節點。 // C# 程式碼   class Node {    public int data;      public Node next;   // 包含序列中下一個                        // 節點地址的變數   }

// C# 程式碼     class List { private Node START; List() { START = NULL; } public void addNode(int element){}   public bool search(int element, ref Node previous, ref Node current) {}   public bool delNode(int element) {}   public void traverse() {}   } cing:"100 50 0";mso-margin-left-alt:219;mso-char-wrap:1;mso-kinsoku-overflow:1'>    

// C# 程式碼 class List   {     Node * START;     public:     List()    {  START = NULL;    }    void addNode(int element);    bool search(int element, Node *previous, Node  *current);    bool delNode(int element);    void traverse();  }; 6;mso-char-wrap:1;mso-kinsoku-overflow:1'>    

問題描述: 編寫一個程式,對單鏈接列表執行插入、搜尋、刪除和遍歷操作,該列 表儲存了班級中的學生記錄。每個記錄具有以下資訊: 學生的註冊號 學生姓名

小結

在本章中,你已經學到: 單鏈接列表中,每個節點包含: 資訊 列表中下一個節點的地址 單鏈接列表只可以按一個方向遍歷。 連結列表中的刪除和插入操作比陣列快,但是,連結列表中的元素訪問速度比數 組要慢。

/*
建立連結串列,通過使用節點類來建立
*/
using System;
class Node
{
	public int 	data;		//data用來存放節點的資料
	public Node 	next;		//next(節點物件)用來存放下一個節點的地址
}

class CreateList
{
	private Node start;		//頭指標存放第一個節點的地址.
	private Node current;	//定義個當前節點物件,用來遍歷連結串列.
	private Node previous;	//前驅節點.
	public CreateList()
	{
		start=null;					//當初始化類時,設定頭指標為空	
	}		
  /*優化的建立連結串列的方法*/
  public void create2()
  {
  	previous=current=start=null;		//最開始的時候都為空,就是連結串列為一個空連結串列.
  	Console.Write("請輸入您要建立連結串列的個數:");
  	int k=Convert.ToInt32(Console.ReadLine());
  	for(int i=0;i<k;i++)
  	{
  		Console.Write("請輸入您要插入的第"+(i+1).ToString()+"節點資料:");	
  		
  		current=new Node();
  		current.data=Convert.ToInt32(Console.ReadLine());	
  		
  			if(start==null)
  				start=current;
  			else
  				previous.next=current;
  			
  			previous=current;  			
  	}
  }
	public void create()		//建立連結串列的方法:笨蛋方法(最為樸素的方法)
	{		Node first=null;
		if(start==null)
		{
			first=new Node();	//1.分配記憶體	
			Console.Write("請輸入第一個節點值:");	
			first.data=Convert.ToInt32(Console.ReadLine());				//2.設定資料域裡面的資料為2
			first.next=null;   //3.設定地址域裡面的地址為null
			start=first;			//4.建連結
			current=first;		//當前節點為第一個節點.
		}
		//------------------新增質數為3的節點
		Node second=new Node();		//1。分配記憶體
		Console.Write("請輸入第二個節點值");
		second.data=Convert.ToInt32(Console.ReadLine());						//2.設資料
		second.next=null;					//3.設地址
		first.next=second;				//4.將第一個節點指向第二個節點	
		//---------------------
		Node third=new Node();		//1。分配記憶體
		Console.Write("請輸入第3個節點值");
		third.data=Convert.ToInt32(Console.ReadLine());						//2.設資料
		third.next=null;					//3.設地址
		second.next=third;				//4.將第2個節點指向第3個節點	
		//---------------------------
		Node fourth=new Node();		//1。分配記憶體
		Console.Write("請輸入第4個節點值");
		fourth.data=Convert.ToInt32(Console.ReadLine());						//2.設資料
		fourth.next=null;					//3.設地址
		third.next=fourth;				//4.將第3個節點指向第4個節點
		//-------------------------
		Node fifth=new Node();		//1。分配記憶體
		Console.Write("請輸入第5個節點值");
		fifth.data=Convert.ToInt32(Console.ReadLine());						//2.設資料
		fifth.next=null;					//3.設地址
		fourth.next=fifth;				//4.將第4個節點指向第5個節點			
	}//建立結束
	public void scan()		//遍歷連結串列的方法
	{
		current=start;		//從頭指標開始
		int i=1;
		while(current!=null)
		{
				Console.WriteLine("第"+i+"個節點資料為:"+current.data);
				current=current.next;		//將當前節點的下一個節點的地址給current,以便current去訪問下一個節點.
				i++;
		}
	}
	//插入新節點
	public void insert()
	{
		Console.WriteLine("請輸入您要插入的新節點資料");
		int i=Convert.ToInt32(Console.ReadLine());
		Node newnode=new Node();
		newnode.data=i;
		
		/*判斷新節點是否為空,並且連結串列當中沒有該節點:在頭節點之前插入*/	
		if((start==null)||(i<=start.data))
		{
			if((start!=null)&&(i==start.data))	
			{
				Console.WriteLine("\n重複節點不允許");
				return;	
			}
			newnode.next=start;
			start=newnode;
			return;
		}
		/*在列表中間插入*/
		previous=current=start;	//設定為開始頭節點
		while((current!=null)&&(i>=current.data))
		{
			if(i==current.data)
			{
				Console.WriteLine("\n重複資料不允許");
				return;	
			}	
			previous=current;
			current=current.next;
		}
		/*找到前驅節點和後繼節點之後*/
		newnode.next=current;
		previous.next=newnode;
	}
	//搜尋連結串列元素
	public void search()
	{
		Console.WriteLine("請輸入您要查詢的資料:");
		int i=Convert.ToInt32(Console.ReadLine());	//1.定義一個要搜尋的數字
		Node current=start;	//2.定義當前節點物件current
		while((current!=null)&&(current.data!=i))	//3.如果當前節點不為空,並且當前節點資料不等於你輸入的資料,則迴圈.
		{				
				current=current.next;
				
		}
		//4。判斷是找到了還是沒有找到
		if(current==null)
			Console.WriteLine("沒有找到");
		else             
			Console.WriteLine("已經找到了您輸入的資料");		
	}
	//刪除連結串列元素
	public void delete()
	{
		Console.WriteLine("請輸入您要刪除的元素");
		int i=Convert.ToInt32(Console.ReadLine());
		
		previous=current=start;		//設定當前節點為頭指標指向的節點.
		
		if(start==null)
		{
			Console.WriteLine("吃飽了撐的,開始亂刪了,滾!XXXXXXXX");	
		}else if(current.data==i)
		{
				start=current.next;
				current.next=null;	//書上忽略了,由系統自動回收不用的記憶體.
		}else		//否則,有多個節點,要刪除我們指定的節點.
		{
				while((current!=null)&&(current.data!=i))
				{//要找下一個符合條件的節點
					previous=current;		//在找符合要求的節點之前,將原來節點的地址給previous
					current=current.next;	//當前節點指向下一個節點.
				}
				if(current==null)
					Console.WriteLine("沒有找到!");
				else
					previous.next=current.next;		//將要刪除的節點的後繼節點地址給前驅節點的next.
		}
		
	}
	/*
	1.定義要刪除節點的前驅節點物件:previous;要刪除節點:current
	2.previous和current都指向start
	3.遍歷連結串列,以便找到要刪除的節點.到底什麼時候遍歷連結串列呢?
	while(current!=null)並且(current.data!=17)
	{
			previous=current;
			current=current.next;	// 要查詢下一個節點,同時,前驅節點指向剛才的current節點.
	}
	如果current==空:null
		說明沒有找到
	else	
		找到了則: previous.next=current.next;
	
	*/
	public static void Main(string[]args)
	{
		CreateList list=new CreateList();
		//list.create2();	//建立連結串列	
		list.scan();
		list.insert();
		list.delete();
		list.scan();
		//list.search();
	}
}



/*
針對連結串列為空的時候進行插入一個節點的實現;
連結串列的實現通過節點類來實現.
*/
using System;
class Node	//定義一個節點類
{
	public int data;		//資料
	public Node next;		//next:存放下一個節點的地址.	
}
//定義實現對節點操作的類
class	LinkedListInsert
{
	//定義一個頭指標的屬性
	private Node Start;
	private Node current;	//當前節點.
	//定義構造方法
	public LinkedListInsert()
	{
		Start=null;	
	}
		
	//定義一個靜態插入方法
	public void insert()
	{ //直接賦值 
		if(Start==null)
		{
				//1.第一步是給新的節點分配記憶體
				Node first= new Node();  //分3步畫圖,記到本上.
				//2.設定新節點的資料為2
				first.data=2;
				//3.將新節點(即質數為2的節點)的地址給頭指標,即頭指標指向質數為2的節點
			  Start=first;
			  //4.設定fist的下一個節點為空(null)
			  first.next=null;	
			  //----------------------下面所實現的就應該再插入一個節點的情況;仍然按照插入第一個節點的情況步驟來做,不同點注意將新的點地址給誰了?     
			  Node second=new Node();
			  second.data=3;
			  first.next =second;   //將質數3的節點放到第一個節點地址域
			  second.next=null;
			  //-----------------------質數為5的節點
			  Node third=new Node();
			  third.data=5;
			  second.next=third;
			  third.next=null;
			  
		}
	}
	//瀏覽節點方法
	public void scan()	
	{
		//需要判斷,如果節點不為空,則輸出節點的資料域裡的內容
		current=Start;
		int i=0;
		while(current!=null)
		{
			Console.WriteLine("第{0}節點的資料為:{1}",i+1,current.data);
			current=current.next;
			i++;
		}
		
	}
	public static void Main(string[]args)
	{
			LinkedListInsert list=new LinkedListInsert();
			list.insert();
			list.scan();
	}
}


/*問題描述:對單鏈接列表執行插入、搜尋、刪除和遍禮操作,該列表儲存了班級中的學生記錄。每個記錄具有如下資訊:
學生的註冊號
學生的姓名
*/
using System;
using System.Text;

namespace Sort
{
	/*每個節點由資訊部分和到下一個節點的地址(連結)組成*/	
	class Node
	{
		public int rollNumber;
		public string name;
		//上面的學號和姓名為資料
		public Node next;	//為地址
	}
	class List
	{
		Node Start;
		public List()
		{
			Start=null;	
		}	
		public void addNode()   /*在列表中新增一個節點*/
		{
			int rollNo;
			string sName;
			Console.Write("\n請輸入學生的學號:");
			rollNo=Convert.ToInt32(Console.ReadLine());
			
			Console.Write("\n請輸入學生的姓名:");
			sName=Console.ReadLine();
				
			Node newNode=new Node();	//宣告新節點並賦值
			newNode.rollNumber=rollNo;
			newNode.name=sName;
			
			/*如果要插入的節點是第一個節點*/
			if((Start==null)||(rollNo<=Start.rollNumber))
			{
				if((Start!=null)&&(rollNo==Start.rollNumber))	
				{
					Console.WriteLine("\n重複的資料不允許被插入");	
					return;
				}//不允許重複的學號.
				newNode.next=Start;
				Start=newNode;
				return;
			}
			/*在列表中找到新節點的位置*/
			Node previous,current;  //定義我們要插入節點的前驅節點和後繼節點物件.
			previous=Start;
			current=Start;
			
			while((current!=null)&&(rollNo>=current.rollNumber))	
			{
				if(rollNo==current.rollNumber)
				{
					Console.WriteLine("\n重複資料不允許\n");
					return;	
				}	
				previous=current;
				current=current.next;
			}
			/*一旦執行上述迴圈,新節點的位置就位於前一個和當前節點之間*/
			newNode.next=current;  //新節點指向後繼節點
			previous.next=newNode;  //將前驅節點指向新節點.
		}	
		/*------------------------------------------------------------------*/	
		public bool delNode(int rollNo)		/*從列表中刪除指定的節點*/
		{
			Node previous,current;
			previous=current=null;
			if(Search(rollNo,ref previous,ref current)==false)	/*檢查指定的節點是否在列表中*/
				return false;
			previous.next=current.next;
			if(current==Start)
				Start=Start.next;
			return true;
		}
		public bool Search(int rollNo,ref Node previous,ref Node current)		/*檢查指定的節點是否在列表中*/
		{
			previous=Start;
			current=Start;
			
			while((current!=null)&&(rollNo!=current.rollNumber))
			{
				previous=current;
				current=current.next;	
			}
			if(current==null)
				return (false);
			else
				return (true);
		}
		/*遍歷該列表*/
		public void traverse()
		{
			if(listEmpty())
				Console.WriteLine("\n列表是空的\n");	
			else
			{
				Console.WriteLine("列表的記錄是:\n");
				Node currentNode;
				for(currentNode=Start;currentNode!=null;currentNode=currentNode.next)
					Console.Write(currentNode.rollNumber+"   "+currentNode.name+"\n");
				Console.WriteLine();	
			}
		}
		/*---------------------判斷列表是否為空-------------------*/
		public bool listEmpty()
		{
			if(Start==null)
				return true;
			else
				return false;	
		}
		/*-----------------------Main()方法--------------------*/
		public static void Main(string[]args)
		{
			List obj=new List();
			while(true)
			{
				try
				{
					Console.WriteLine("\n選單");
					Console.WriteLine("1.增加列表");
					Console.WriteLine("2.刪除列表");
					Console.WriteLine("3.遍歷列表");
					Console.WriteLine("4.搜尋列表");
					Console.WriteLine("5.退出");
					Console.Write("\n請輸入您的選擇(1-5):");
					char ch=Convert.ToChar(Console.ReadLine());
					switch(ch)
					{
						case '1':
							obj.addNode();
							break;
						case '2':
							{
								if(obj.listEmpty())
									{
										Console.WriteLine("\n列表為空");
										break;	
								  }
							Console.Write("\n請輸入您要刪除學生記錄的學號:");
							int rollNo=Convert.ToInt32(Console.ReadLine());
							Console.WriteLine();
							if(obj.delNode(rollNo)==false)
								Console.WriteLine("\n記錄沒有發現.");
							else
								Console.WriteLine("記錄為"+rollNo+"已經刪除");
							}
							break;
						case '3':
							obj.traverse();
							break;
						case '4':
							{
								if(obj.listEmpty()==true)
								{
									Console.WriteLine("\n列表為空");
									break;	
								}									
								Node previous,current;
								previous=current=null;
								Console.Write("\n請輸入您要搜尋學生的學號:");
								int num=Convert.ToInt32(Console.ReadLine());
																
								if(obj.Search(num,ref previous,ref current)==false)
									Console.WriteLine("\n記錄沒有發現.");
								else
									{
										Console.WriteLine("\n記錄發現");
										Console.WriteLine("\n學號為:"+current.rollNumber);
										Console.WriteLine("\n姓名:"+current.name);	
									}			
							}
							break;
						case '5':
							return;
						default:
							Console.WriteLine("\n無效選項");
							break;
					}					
				}
				catch(Exception e)
				{
					Console.WriteLine("\n請檢查輸入的值.");
				}	
			}	
		}
	}
}