只考慮線性表的基本操做,因此以C#接口的形式表示線性表,接口中的方法成員表示基本操做。node
public interface IListDS<T> { T this[int index] { get; } //索引器 int GetLength(); //求長度 void Clear(); //清空操做 bool IsEmpty(); //判斷線性表是否爲空 void Append(T item); //附加操做 void Insert(T item, int i); //插入操做 T Delete(int i); //刪除操做 T GetElem(int i); //取表元 int Locate(T value); //按值查找 }
爲了和.NET框架中的接口IList相區分,在IList後面加了「DS」,「DS」表示數據結構。
一、求長度:GetLength()
初始條件:線性表存在;
操做結果:返回線性表中全部數據元素的個數。數組
二、清空操做:Clear()
初始條件:線性表存在且有數據元素;
操做結果:從線性表中清除全部數據元素,線性表爲空。數據結構
三、判斷線性表是否爲空:IsEmpty()
初始條件:線性表存在;
操做結果:若是線性表爲空返回true,不然返回false。框架
四、附加操做:Append(T item)
初始條件:線性表存在且未滿;
操做結果:將值爲item的新元素添加到表的末尾。ide
五、插入操做:Insert(T item, int i)
初始條件:線性表存在,插入位置正確()(1≤i≤n+1,n爲插入前的表長)。
操做結果:在線性表的第i個位置上插入一個值爲item的新元素,這樣使得原序號爲i,i+1,…,n的數據元素的序號變爲i+1,i+2,…,n+1,插入後表長=原表長+1。測試
六、刪除操做:Delete(int i)
初始條件:線性表存在且不爲空,刪除位置正確(1≤i≤n,n爲刪除前的表長)。
操做結果:在線性表中刪除序號爲i的數據元素,返回刪除後的數據元素。
刪除後使原序號爲i+1,i+2,…,n的數據元素的序號變爲i,i+1,…,n-1,刪除後表長=原表長-1。ui
七、取表元:GetElem(int i)
初始條件:線性表存在,所取數據元素位置正確(1≤i≤n,n爲線性表的表長);
操做結果:返回線性表中第i個數據元素。this
八、按值查找:Locate(T value)
初始條件:線性表存在。
操做結果:在線性表中查找值爲value的數據元素,其結果返回在線性表中首次出現的值爲value的數據元素的序號,稱爲查找成功;不然,在線性表中未找到值爲value的數據元素,返回一個特殊值表示查找失敗。指針
/// <summary> /// 順序表 /// </summary> /// <typeparam name="T"></typeparam> public class SeqList<T> : IListDS<T> { private int count = 0; //順序表的容量 private T[] data; //數組,用於存儲順序表中的數據元素 private int last; //指示順序表最後一個元素的位置 //索引器 public T this[int index] { get { return data[index]; } set { data[index] = value; } } public SeqList(int size) { data = new T[size]; count = 0; } public SeqList():this(10) { } public int GetLength() { return count; } public void Clear() { this.count = 0; } public bool IsEmpty() { return count == 0; } public void Append(T item) { if (count == data.Length)// 當前數組存滿 { Console.WriteLine("List is full"); } else { data[count] = item; count++; } } public void Insert(T item, int i) { if (count == data.Length)// 當前數組存滿 { Console.WriteLine("List is full"); } else { //這裏由於從插入位置日後覆蓋,第1個被覆蓋索引數據就不見了。因此須要從最後面往前覆蓋 for (int j = count-1; j >= i; j--) { data[j + 1] = data[j]; } data[i] = item; count++; } } public T Delete(int i) { T temp = data[i]; for (int j = i+1; j < count; j++) { data[j - 1] = data[j]; } count--; return temp; } public T GetElem(int i) { //索引存在 if (i >= 0 && i <= count-1) { return data[i]; } else { Console.WriteLine("索引不存在"); return default(T); } } public int Locate(T value) { for (int i = 0; i < data.Length; i++) { if (value.Equals(data[i])) { return i; } } return -1; } }
class Program { static void Main(string[] args) { SeqList<string> seqList = new SeqList<string>(); seqList.Append("123"); seqList.Append("456"); seqList.Append("789"); Console.Write("第一次加載:"); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write($"{seqList[i]},"); } Console.WriteLine(); Console.WriteLine($"第0位置元素:{seqList.GetElem(0)}"); Console.WriteLine($"第0索引:{seqList[0]}"); Console.Write("Insert後的數據:"); seqList.Insert("999", 1); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write($"{seqList[i]},"); } Console.WriteLine(); Console.Write("Delete(0)後的數據:"); seqList.Delete(0); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write($"{seqList[i]},"); } Console.WriteLine(); Console.Write("清空後:"); seqList.Clear(); Console.WriteLine(seqList.GetLength()); Console.Read(); } }
在對順序表進行插入和刪除時,須要經過移動數據元素來實現,影響了運行效率。本節介紹線性表的另一種存儲結構——鏈式存儲(Linked Storage),這樣的線性表叫鏈表(Linked List)。鏈表不要求邏輯上相鄰的數據元素在物理存儲位置上也相鄰,所以,在對鏈表進行插入和刪除時不須要移動數據元素,但同時也失去了順序表可隨機存儲的優勢。code
鏈表是用一組任意的存儲單元來存儲線性表中的數據元素(這組存儲單元能夠是連續的,也能夠是不連續的)。那麼,怎麼表示兩個數據元素邏輯上的相鄰關係呢?即如何表示數據元素之間的線性關係呢?爲此,在存儲數據元素時,除了存儲數據元素自己的信息外,還要存儲與它相鄰的數據元素的存儲地址信息。這兩部分信息組成該數據元素的存儲映像(Image),稱爲結點(Node)。把存儲據元素自己信息的域叫結點的數據域(Data Domain),把存儲與它相鄰的數據元素的存儲地址信息的域叫結點的引用域(Reference Domain)。所以,線性表經過每一個結點的引用域造成了一根「鏈條」,這就是「鏈表」名稱的由來。
若是結點的引用域只存儲該結點直接後繼結點的存儲地址,則該鏈表叫單鏈表(Singly Linked List)。把該引用域叫 next。單鏈表結點的結構如圖所示,圖中 data 表示結點的數據域。
插入的狀況解析
新建節點類,注意data與next
// <summary> /// 單鏈表節點 /// </summary> /// <typeparam name="T"></typeparam> public class Node<T> { public T data { get; set; } //(數據域),存儲數據自己 public Node<T> next { get; set; } //(引用域)指針,用來指向下一個元素 public Node(T data) { this.data = data; this.next = null; } public Node(Node<T> next) { this.next = next; } public Node(T data, Node<T> next) { this.data = data; this.next = next; } }
/// <summary> /// 單鏈表 /// </summary> /// <typeparam name="T"></typeparam> class SingleLinkList<T> : IListDS<T> { private Node<T> header; public int GetLength() { Node<T> p = header; int len = 0; while (p != null) { ++len; p = p.next; } return len; } public void Clear() { header = null; } public bool IsEmpty() { return header == null; } public void Append(T item) { Node<T> newNode = new Node<T>(item); //若是頭節點爲空,新節點爲頭節點 if (header == null) { header = newNode; } else { Node<T> headNode = header; //下個節點爲空時,說明是末尾節點。 while (headNode.next != null) { headNode = headNode.next; } //把須要添加的node,添加給末尾指針next headNode.next = newNode; } } public void Insert(T item, int i) { //從第1個節點開始 if (i == 0) { //頭節點不容許插入 Console.WriteLine("List is empty or Position is error"); } else if (i == 1) { //若是是第1個節點 Node<T> newNode = new Node<T>(item); newNode.next = header.next; header.next = newNode; } else { Node<T> tempNode = header; int j = 1; //讓temp從頭開始向後移動到下一個位置,移動到插入前一個位置爲止。 while (tempNode.next != null && j < i) { tempNode = tempNode.next; j++; } if (j == i) { Node<T> newNode = new Node<T>(item); //行插入節點 Node<T> preNode = tempNode; //前節點 Node<T> currentNode = tempNode.next; //當前節點 newNode.next = currentNode; //插入節點的指針 preNode.next = newNode; //前節點指針 } } } public T Delete(int i) { //從第1個節點開始 if (i == 0) { T current = header.data; header = header.next; return current; } else { Node<T> tempNode = header; int j = 1; //讓temp從頭開始向後移動到下一個位置,移動到插入前一個位置爲止。 while (tempNode.next != null && j < i) { tempNode = tempNode.next; j++; } if (j == i) { Node<T> preNode = tempNode; //前節點 Node<T> currentNode = preNode.next; //前節點 Node<T> nexNode = tempNode.next.next; //下一個節點 preNode.next = nexNode; //前節點指針 return currentNode.data; } return default(T); } } public T GetElem(int i) { return this[i]; } public int Locate(T value) { if (IsEmpty()) { return -1; } else { Node<T> p = header; int i = 1; while (!p.data.Equals(value) && p.next != null) { p = p.next; ++i; } return i; } } public T this[int index] { get { Node<T> p = header; int len = 0; while (p != null && len < index) { ++len; p = p.next; } return p.data; } } public override string ToString() { StringBuilder builder = new StringBuilder(); builder.Append("單鏈表:"); Node<T> p = header; int len = 0; while (p != null) { builder.Append($"{p.data.ToString()},"); ++len; p = p.next; } return builder.ToString(); } }
class Program { static void Main(string[] args) { SingleLinkList<int> singleLink = new SingleLinkList<int>(); singleLink.Append(100); singleLink.Append(200); singleLink.Append(300); singleLink.Append(400); singleLink.Append(500); singleLink.Append(600); Console.WriteLine(singleLink.ToString()); singleLink.Insert(900, 3); Console.WriteLine("插入:900 index:3"); Console.WriteLine(singleLink.ToString()); Console.WriteLine("刪除index:4,刪除的數據爲:" + singleLink.Delete(4)); Console.WriteLine(singleLink.ToString()); Console.WriteLine("如今長度:" + singleLink.GetLength()); Console.WriteLine("索引5的數據:" + singleLink[5]); } }
前面介紹的單鏈表容許從一個結點直接訪問它的後繼結點,因此, 找直接後繼結點的時間複雜度是 O(1)。可是,要找某個結點的直接前驅結點,只能從表的頭引用開始遍歷各結點。若是某個結點的 Next 等於該結點,那麼,這個結點就是該結點的直接前驅結點。也就是說,找直接前驅結點的時間複雜度是 O(n), n是單鏈表的長度。固然,咱們也能夠在結點的引用域中保存直接前驅結點的地址而不是直接後繼結點的地址。這樣,找直接前驅結點的時間複雜度只有 O(1),但找直接後繼結點的時間複雜度是 O(n)。若是但願找直接前驅結點和直接後繼結點的時間複雜度都是 O(1),那麼,須要在結點中設兩個引用域,一個保存直接前驅結點的地址,叫 prev,一個直接後繼結點的地址,叫 next,這樣的鏈表就是雙向鏈表(Doubly Linked List)。雙向鏈表的結點結構示意圖如圖所示。
public class DbNode<T> { private T data; //數據域 private DbNode<T> prev; //前驅引用域 private DbNode<T> next; //後繼引用域 //構造器 public DbNode(T val, DbNode<T> p) { data = val; next = p; } //構造器 public DbNode(DbNode<T> p) { next = p; } //構造器 public DbNode(T val) { data = val; next = null; } ![
有些應用不須要鏈表中有明顯的頭尾結點。在這種狀況下,可能須要方便地從最後一個結點訪問到第一個結點。此時,最後一個結點的引用域不是空引用,而是保存的第一個結點的地址(若是該鏈表帶結點,則保存的是頭結點的地址),也就是頭引用的值。帶頭結點的循環鏈表(Circular Linked List)如圖所示。