C#資料結構與演算法1-C# 線性表

firespeed發表於2024-10-26

一 什麼是線性表

線性表是最簡單、最基本、最常用的資料結構。線性表是線性結構的抽象(Abstract),線性結構的特點是結構中的資料元素之間存在一對一的線性關係。這種一對一的關係指的是資料元素之間的位置關係,即:( 1)除第一個位置的資料元素外,其它資料元素位置的前面都只有一個資料元素;( 2)除最後一個位置的資料元素外,其它資料元素位置的後面都只有一個元素。也就是說,資料元素是一個接一個的排列。因此,可以把線性表想象為一種資料元素序列的資料結構。

線性表就是位置有先後關係,一個接著一個排列的資料結構。

1 CLR中的線性表

c# 1.1 提供了一個非泛型介面IList介面,介面中的項是object,實現了IList解釦子的類有ArrayList,ListDictionary,StringCollection,StringDictionary.

c# 2.0 提供了泛型的IList介面,實現了List介面的類有List

2 線性表的介面定義

public interface IListDS<T> {
int GetLength(); //求長度
void Clear(); //清空操作
bool IsEmpty(); //判斷線性表是否為空
void Add(T item); //附加操作
void Insert(T item, int i); //插入操作
T Delete(int i); //刪除操作
T GetElem(int i); //取表元
T this[int index]{get;}//定義一個索引器 獲取元素
int Locate(T value); //按值查詢
}

二 線性表的實現方式

線性表的實現方式有下面幾種
順序表
單連結串列
雙向連結串列
迴圈連結串列

1 順序表

在計算機內,儲存線性表最簡單、最自然的方式,就是把表中的元素一個接一個地放進順序的儲存單元,這就是線性表的順序儲存(Sequence Storage)。線性表的順序儲存是指在記憶體中用一塊地址連續的空間依次存放線性表的資料元素,用這種方式儲存的線性表叫順序表(Sequence List),如圖所示。順序表的特點是表中相鄰的資料元素在記憶體中儲存位置也相鄰。

2 順序表的儲存

*假設順序表中的每個資料元素佔w個儲存單元,設第i個資料元素的儲存地址為Loc(ai),則有:
Loc(ai)= Loc(a1)+(i-1)w 1≤i≤n式中的Loc(a1)表示第一個資料元素a1的儲存地址,也是順序表的起始儲存地址,稱為順序表的基地址(Base Address)。也就是說,只要知道順序表的基地址和每個資料元素所佔的儲存單元的個數就可以求出順序表中任何一個資料元素的儲存地址。並且,由於計算順序表中每個資料元素儲存地址的時間相同,所以順序表具有任意存取的特點。(可以在任意位置存取東西)
C#語言中的陣列在記憶體中佔用的儲存空間就是一組連續的儲存區域,因此,陣列具有任意存取的特點。所以,陣列天生具有表示順序表的資料儲存區域的特性。

3 順序表的實現

public class SeqList<T> : IListDS<T> {
// ...
}

4 單連結串列

順序表是用地址連續的儲存單元順序儲存線性表中的各個資料元素,邏輯上相鄰的資料元素在物理位置上也相鄰。因此,在順序表中查詢任何一個位置上的資料元素非常方便,這是順序儲存的優點。但是,在對順序表進行插入和刪除時,需要透過移動資料元素來實現,影響了執行效率。線性表的另外一種儲存結構——鏈式儲存(Linked Storage),這樣的線性表叫連結串列(Linked List)。連結串列不要求邏輯上相鄰的資料元素在物理儲存位置上也相鄰,因此,在對連結串列進行插入和刪除時不需要移動資料元素,但同時也失去了順序表可隨機儲存的優點。

5 單連結串列的粗儲存

連結串列是用一組任意的儲存單元來儲存線性表中的資料元素(這組儲存單元可以是連續的,也可以是不連續的)。那麼,怎麼表示兩個資料元素邏輯上的相鄰關係呢?即如何表示資料元素之間的線性關係呢?為此,在儲存資料元素時,除了儲存資料元素本身的資訊外,還要儲存與它相鄰的資料元素的儲存地址資訊。這兩部分資訊組成該資料元素的儲存映像(Image),稱為結點(Node)。把儲存據元素本身資訊的域叫結點的資料域(Data Domain),把儲存與它相鄰的資料元素的儲存地址資訊的域叫結點的引用域(Reference Domain)。因此,線性表透過每個結點的引用域形成了一根“鏈條”,這就是“連結串列”名稱的由來。
如果結點的引用域只儲存該結點直接後繼結點的儲存地址,則該連結串列叫單連結串列(Singly Linked List)。把該引用域叫 next。單連結串列結點的結構如圖所示,圖中 data 表示結點的資料域。

6 鏈式儲存結構

下圖是線性表(a1,a2,a3,a4,a5,a6)對應的鏈式儲存結構示意圖。

另外一種表示形式:

7 單連結串列節點定義

public class Node<T>
{
private T data; //資料域
private Node<T> next; //引用域
//構造器
public Node(T val, Node<T> p)
{
data = val;
next = p;
}
//構造器
public Node(Node<T> p)
{
next = p;
}
//構造器
public Node(T val)
{
data = val;
next = null;
}
//構造器
public Node()
{
data = default(T);
next = null;
}
//資料域屬性
public T Data
{
get
{
return data;
}
set
{
data = value;
}
}
//引用域屬性
public Node<T> Next
{
get
{
return next;
}
set
{
next = value;
}
}
}


8 單連結串列實現

using System;

public class LinkList<T> : IListDS<T>
{
private Node<T> head; //單連結串列的頭引用
//頭引用屬性
public Node<T> Head
{
get { return head; }
set { head = value; }
}

//構造器
public LinkList()
{
head = null;
}

//求單連結串列的長度
public int GetLength()
{
Node<T> p = head;
int len = 0;
while (p != null)
{
++len;
p = p.Next;
}
return len;
}

//清空單連結串列
public void Clear()
{
head = null;
}

//判斷單連結串列是否為空
public bool IsEmpty()
{
if (head == null)
{
return true;
}
else
{
return false;
}
}

//在單連結串列的末尾新增新元素
public void Append(T item)
{
Node<T> q = new Node<T>(item);
Node<T> p = new Node<T>();
if (head == null)
{
head = q;
return;
}
p = head;
while (p.Next != null)
{
p = p.Next;
}
p.Next = q;
}

//在單連結串列的第i個結點的位置前插入一個值為item的結點
public void Insert(T item, int i)
{
if (IsEmpty() || i < 1)
{
Console.WriteLine("List is empty or Position is error!");
return;
}
if (i == 1)
{
Node<T> q = new Node<T>(item);
q.Next = head;
head = q;
return;
}
Node<T> p = head;
Node<T> r = new Node<T>();
int j = 1;
while (p.Next != null && j < i)
{
r = p;
p = p.Next;
++j;
}
if (j == i)
{
Node<T> q = new Node<T>(item);
q.Next = p;
r.Next = q;
}
}

//在單連結串列的第i個結點的位置後插入一個值為item的結點
public void InsertPost(T item, int i)
{
if (IsEmpty() || i < 1)
{
Console.WriteLine("List is empty or Position is error!");
return;
}
if (i == 1)
{
Node<T> q = new Node<T>(item);
q.Next = head.Next;
head.Next = q;
return;
}
Node<T> p = head;
int j = 1;
while (p != null && j < i)
{
p = p.Next;
++j;
}
if (j == i)
{
Node<T> q = new Node<T>(item);
q.Next = p.Next;
p.Next = q;
}
}

//刪除單連結串列的第i個結點
public T Delete(int i)
{
if (IsEmpty() || i < 0)
{
Console.WriteLine("Link is empty or Position is error!");
return default(T);
}
Node<T> q = new Node<T>();

if (i == 1)
{
q = head;
head = head.Next;
return q.Data;
}
Node<T> p = head;
int j = 1;
while (p.Next != null && j < i)
{
++j;
q = p;
p = p.Next;
}
if (j == i)
{
q.Next = p.Next;
return p.Data;
}
else
{
Console.WriteLine("The ith node is not exist!");
return default(T);
}
}

//獲得單連結串列的第i個資料元素
public T GetElem(int i)
{
if (IsEmpty())
{
Console.WriteLine("List is empty!");
return default(T);
}
Node<T> p = new Node<T>();
p = head;
int j = 1;
while (p.Next != null && j < i)
{
++j;
p = p.Next;
}
if (j == i)
{
return p.Data;
}
else
{
Console.WriteLine("The ith node is not exist!");
return default(T);
}
}

//在單連結串列中查詢值為value的結點
public int Locate(T value)
{
if (IsEmpty())
{
Console.WriteLine("List is Empty!");
return -1;
}
Node<T> p = new Node<T>();
p = head;
int i = 1;
while (!p.Data.Equals(value) && p.Next != null)
{
P = p.Next;
++i;
}
return i;
}
}

8 雙向連結串列

前面介紹的單連結串列允許從一個結點直接訪問它的後繼結點,所以, 找直接後繼結點的時間複雜度是 O(1)。但是,要找某個結點的直接前驅結點,只能從表的頭引用開始遍歷各結點。如果某個結點的 Next 等於該結點,那麼,這個結點就是該結點的直接前驅結點。也就是說,找直接前驅結點的時間複雜度是 O(n), n是單連結串列的長度。當然,我們也可以在結點的引用域中儲存直接前驅結點的地址而不是直接後繼結點的地址。這樣,找直接前驅結點的時間複雜度只有 O(1),但找直接後繼結點的時間複雜度是 O(n)。如果希望找直接前驅結點和直接後繼結點的時間複雜度都是 O(1),那麼,需要在結點中設兩個引用域,一個儲存直接前驅結點的地址,叫 prev,一個直接後繼結點的地址,叫 next,這樣的連結串列就是雙向連結串列(Doubly Linked List)。雙向連結串列的結點結構示意圖如圖所示。

9 雙向連結串列節點實現

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;
}

//構造器
public DbNode()
{
data = default(T);
next = null;
}

//資料域屬性
public T Data
{
get { return data; }
set { data = value; }
}

//前驅引用域屬性
public DbNode<T> Prev
{
get { return prev; }
set { prev = value; }
}

//後繼引用域屬性
public DbNode<T> Next
{
get { return next; }
set { next = value; }
}
}

10 雙向連結串列插入示意圖

11 迴圈連結串列

有些應用不需要連結串列中有明顯的頭尾結點。在這種情況下,可能需要方便地從最後一個結點訪問到第一個結點。此時,最後一個結點的引用域不是空引用,而是儲存的第一個結點的地址(如果該連結串列帶結點,則儲存的是頭結點的地址),也就是頭引用的值。帶頭結點的迴圈連結串列(Circular Linked List)如圖所示。

https://mp.weixin.qq.com/s/LeHtBv7g6RBaTR--97eJ-g

相關文章