數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)分類(DataStructure)
- 數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合。
- 在任何問題中,數(shù)據(jù)元素之間不是孤立的,而是存在一定關(guān)系,這種關(guān)系稱為結(jié)構(gòu)(structure)。
根據(jù)數(shù)據(jù)元素之間關(guān)系的不同特性,可分為4種基本數(shù)據(jù)結(jié)構(gòu):
- 集合(set)集合中的數(shù)據(jù)元素除了存在“同屬于一個(gè)集合”的關(guān)系外,不存在任何其他關(guān)系。
- 線性結(jié)構(gòu)(linear structure)線性結(jié)構(gòu)中數(shù)據(jù)元素存在著一對(duì)一的關(guān)系
- 樹形結(jié)構(gòu)(tree structure)樹形結(jié)構(gòu)中數(shù)據(jù)元素存在一對(duì)多的關(guān)系
- 圖形結(jié)構(gòu)(graphic structure)圖形結(jié)構(gòu)中數(shù)據(jù)元素存在多對(duì)多的關(guān)系

算法
- 算法可理解為基本運(yùn)算及規(guī)定的運(yùn)算順序所構(gòu)成的完整的解題步驟
- 算法可視為按照要求設(shè)計(jì)好的有限的確切的計(jì)算序列,并按步驟和序列解決某類問題。
數(shù)據(jù)結(jié)構(gòu)和算法之間的關(guān)系
- 數(shù)據(jù)結(jié)構(gòu)可認(rèn)為是數(shù)據(jù)在程序中的存儲(chǔ)結(jié)構(gòu)和基本數(shù)據(jù)操作
- 算法是用來解決問題的,算法是基于數(shù)據(jù)結(jié)構(gòu)的。
- 數(shù)據(jù)結(jié)構(gòu)是問題的核心,是算法的基礎(chǔ)。
算法的評(píng)價(jià)標(biāo)準(zhǔn)
- 運(yùn)行時(shí)間(running time)
- 占用空間(storage space)有時(shí)需要犧牲空間換取時(shí)間,有時(shí)需要犧牲時(shí)間換取空間。
- 正確性(correctness)
- 可讀性(readability)
- 健壯性(robustness)
線性表
線性表是線性結(jié)構(gòu)的抽象(abstruct),其特點(diǎn)是結(jié)構(gòu)中的數(shù)據(jù)元素之間存在一對(duì)一的線性關(guān)系,這種一對(duì)一的關(guān)系指的是數(shù)據(jù)元素之間的位置關(guān)系。
- 除第一個(gè)位置的數(shù)據(jù)元素外,其他數(shù)據(jù)元素位置的前面都只有一個(gè)數(shù)據(jù)元素。
- 除最后一個(gè)位置的數(shù)據(jù)元素外,其他數(shù)據(jù)元素的后面都只有一個(gè)元素。
也就是說,數(shù)據(jù)元素是一個(gè)接著一個(gè)排列的。因此,可把線性表現(xiàn)象成一種數(shù)據(jù)元素序列的數(shù)據(jù)結(jié)構(gòu)。線性表就是位置有先后關(guān)系,一個(gè)接著一個(gè)排列的數(shù)據(jù)結(jié)構(gòu)。
CLR中的線性表
C#1.1提供了一個(gè)非泛型接口IList接口,IList接口中的項(xiàng)是object,實(shí)現(xiàn)了IList接口的子類有
- ArrayList
- ListDictionary
- StringCollection
- StringDictionary
C#2.0提供了泛型的IList<T>接口,實(shí)現(xiàn)IList<T>接口的類有List<T>。
List<string> list = new List<string>();
list.Add("alice");
list.Add("ben");
list.Add("carl");
Console.WriteLine(list[0]);//根據(jù)索引器訪問元素
list.Remove("ben");
Console.WriteLine(list.Count);
list.Clear();
Console.WriteLine(list.Count);
Console.ReadKey();
實(shí)現(xiàn)線性表接口定義
namespace DataStructure
{
/// <summary>
/// 線性表接口定義
/// </summary>
/// <typeparam name="T"></typeparam>
interface IList<T>
{
/// <summary>
/// 獲取線性表長度,即元素個(gè)數(shù)。
/// </summary>
/// <returns></returns>
int Count();
/// <summary>
/// 清空線性表
/// </summary>
void Clear();
/// <summary>
/// 判斷線性表是否為空
/// </summary>
/// <returns></returns>
bool IsEmpty();
/// <summary>
/// 線性表插入元素
/// </summary>
/// <param name="item">數(shù)據(jù)項(xiàng)</param>
/// <param name="index">數(shù)據(jù)索引</param>
void Insert(T item, int index);
/// <summary>
/// 線性表追加元素
/// </summary>
/// <param name="item">數(shù)據(jù)項(xiàng)</param>
void Append(T item);
/// <summary>
/// 刪除線性表元素
/// 根據(jù)索引刪除指定位置的元素
/// </summary>
/// <param name="index">元素位置索引</param>
/// <returns></returns>
T Delete(int index);
/// <summary>
/// 根據(jù)索引獲取線性表元素
/// </summary>
/// <param name="index">元素索引</param>
/// <returns></returns>
T Get(int index);
/// <summary>
/// 索引器
/// 根據(jù)索引訪問元素
/// </summary>
/// <param name="index">元素索引</param>
/// <returns></returns>
T this[int index] { get; }
/// <summary>
/// 根據(jù)值獲取索引值
/// </summary>
/// <param name="value">元素值</param>
/// <returns></returns>
int Location(T value);
}
}
線性表的實(shí)現(xiàn)方式
- 順序表
- 單鏈表
- 雙向鏈表
- 循環(huán)鏈表
順序表
計(jì)算機(jī)內(nèi)存中保存線性表最簡單自然的方式,是把線性表中的元素一個(gè)接著一個(gè)放入順序的存儲(chǔ)單元中,這就是線性表的順序存儲(chǔ)(sequence storage)。
線性表的順序存儲(chǔ)指的是在內(nèi)存中使用一塊地址連續(xù)的空間依次存放線性表的數(shù)據(jù)元素,使用這種方式存放的線性表叫做順序表(sequence list)。
順序表的特點(diǎn)是表中相鄰的數(shù)據(jù)元素在內(nèi)存中存儲(chǔ)的位置是相鄰的。

順序表的任意存儲(chǔ)
假設(shè):順序表中每個(gè)數(shù)據(jù)元素占用 w 個(gè)存儲(chǔ)單元,設(shè)第 i 個(gè)數(shù)據(jù)元素的存儲(chǔ)地址為 Loc(ai),則有
Loc(ai) = Loc(a1) + (i-1)*w
1<= i <= n式中的Loc(ai) 表示第1個(gè)數(shù)據(jù)元素 a1 的存儲(chǔ)地址,也就是順序表的起始存儲(chǔ)地址,成為順序表的基地址(base address)。
也就是說,只要知道順序表的基地址和每個(gè)數(shù)據(jù)元素所占的存儲(chǔ)單元的個(gè)數(shù),就可以求出順序表中任意一個(gè)數(shù)據(jù)元素的存儲(chǔ)地址。
由于計(jì)算順序表中每個(gè)數(shù)據(jù)元素存儲(chǔ)地址的時(shí)間是相同的,所以順序表具有任意存取的特定,即可以在任意位置存取數(shù)據(jù)。
C#中的數(shù)組在內(nèi)存中占用的存儲(chǔ)空間是一組連續(xù)的存儲(chǔ)區(qū)域。因此,數(shù)組具有任意存取的特點(diǎn)。所以,數(shù)組天生具有表示順序表的數(shù)據(jù)存儲(chǔ)區(qū)域的特性。
namespace DataStructure
{
/// <summary>
/// 順序表
/// </summary>
/// <typeparam name="T"></typeparam>
class SequenceList<T> : IList<T>
{
/// <summary>
/// 用于存取數(shù)據(jù)的數(shù)組
/// </summary>
private T[] data;
/// <summary>
/// 用于記錄存取元素的個(gè)數(shù)
/// </summary>
private int count = 0;
/// <summary>
/// 自定義構(gòu)造器
/// 不提供自動(dòng)擴(kuò)容
/// </summary>
/// <param name="maxsize">元素最大個(gè)數(shù)</param>
public SequenceList(int maxsize)
{
data = new T[maxsize];
count = 0;
}
/// <summary>
/// 默認(rèn)構(gòu)造器
/// 默認(rèn)容量為10
/// </summary>
public SequenceList():this(10)
{
}
/// <summary>
/// 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get
{
return Get(index);
}
}
/// <summary>
/// 追加元素
/// </summary>
/// <param name="item"></param>
public void Append(T item)
{
//判斷數(shù)組是否飽和
if(count == data.Length)
{
System.Console.WriteLine("當(dāng)前順序表已存滿,禁止存入數(shù)據(jù)。");
}
else
{
data[count] = item;
count++;
}
//todo:返回當(dāng)前數(shù)組的索引
}
/// <summary>
/// 清空數(shù)據(jù)
/// </summary>
public void Clear()
{
count = 0;
}
/// <summary>
/// 獲取數(shù)據(jù)個(gè)數(shù)
/// </summary>
/// <returns>數(shù)據(jù)個(gè)數(shù)</returns>
public int Count()
{
return count;
}
/// <summary>
/// 根據(jù)索引刪除元素
/// </summary>
/// <param name="index"></param>
public T Delete(int index)
{
T item = data[index];
//把數(shù)據(jù)向前移動(dòng)
for(int i = index+1;i<count; i++)
{
data[i - 1] = data[i];
}
count--;
return item;
}
/// <summary>
/// 根據(jù)索引獲取值
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T Get(int index)
{
//判斷索引是否存在
if(index >= 0 && index <= count - 1)
{
return data[index];
}
else
{
System.Console.WriteLine("當(dāng)前順序表中索引不存在");
//返回T類型的默認(rèn)值
return default(T);
}
}
/// <summary>
/// 插入元素
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
public void Insert(T item, int index)
{
//從后向前
for(int i = count - 1; i >= index; i--)
{
data[i + 1] = data[i];
}
data[index] = item;
count++;
}
/// <summary>
/// 判斷是否為空
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return count == 0;
}
/// <summary>
/// 根據(jù)值獲取索引
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public int Location(T value)
{
for(int i=0; i<count; i++)
{
if (data[i].Equals(value))
{
return i;
}
}
return -1;
}
}
}
順序表是用地址連續(xù)的存儲(chǔ)單元順序存儲(chǔ)線性表中的各個(gè)數(shù)據(jù)元素,邏輯上相鄰的數(shù)據(jù)元素在物理位置上也是相鄰的。
- 優(yōu)點(diǎn):在順序表中查找任何一個(gè)位置上的數(shù)據(jù)元素快速高效,這是順序存儲(chǔ)的優(yōu)點(diǎn)。
- 缺點(diǎn):在對(duì)順序表進(jìn)行插入和刪除時(shí),需通過移動(dòng)數(shù)據(jù)元素來實(shí)現(xiàn),影響運(yùn)行效率。
- 特點(diǎn):存取快,插入刪除慢。
鏈表
線性表的另一種存儲(chǔ)結(jié)構(gòu) - 鏈?zhǔn)酱鎯?chǔ)(LinkedStorage),這種線性表叫做鏈表(LinkedList)。
鏈表不要求邏輯上相鄰的數(shù)據(jù)元素在物理存儲(chǔ)位置上也相鄰
- 優(yōu)點(diǎn):在對(duì)鏈表進(jìn)行插入和刪除時(shí)無需移動(dòng)數(shù)據(jù)元素
- 缺點(diǎn):鏈表因此失去了順序表可隨機(jī)存儲(chǔ)的優(yōu)勢(shì)
- 特點(diǎn):插入刪除較快,查找較慢。
鏈表節(jié)點(diǎn)
鏈表是用一組任意的存儲(chǔ)單元來存儲(chǔ)線性表中的數(shù)據(jù)元素,這組存儲(chǔ)單元可以是連續(xù),有可以不是連續(xù)的。那么,如何表示兩個(gè)數(shù)據(jù)元素邏輯上是相鄰的關(guān)系呢?也就是說,如何表示數(shù)據(jù)元素之間的線性關(guān)系呢?
為此,在存儲(chǔ)數(shù)據(jù)元素時(shí),除了存儲(chǔ)數(shù)據(jù)本身的元信息外,還要存儲(chǔ)與它相鄰的數(shù)據(jù)元素的存儲(chǔ)地址。這兩部分信息組成該數(shù)據(jù)元素的存儲(chǔ)映像(image),稱之為節(jié)點(diǎn)(node)。

我們把存儲(chǔ)數(shù)據(jù)元素本身的域稱為節(jié)點(diǎn)的數(shù)據(jù)域(data domain),把存儲(chǔ)與之相鄰的數(shù)據(jù)元素的存儲(chǔ)地址信息的域稱為節(jié)點(diǎn)的引用域(reference domain)。
因此,線性表通過每個(gè)節(jié)點(diǎn)的引用域形成了一根“鏈條”,這就是鏈表名稱的由來。

namespace DataStructure
{
/// <summary>
/// 鏈表節(jié)點(diǎn)
/// </summary>
/// <typeparam name="T"></typeparam>
class Node<T>
{
/// <summary>
/// 存儲(chǔ)的數(shù)據(jù)
/// </summary>
private T data;
/// <summary>
/// 指針
/// 用于指向下一個(gè)元素
/// </summary>
private Node<T> next;
/// <summary>
/// 默認(rèn)構(gòu)造器
/// </summary>
public Node()
{
data = default(T);
next = null;
}
public Node(T value)
{
data = value;
next = null;
}
public Node(Node<T> next)
{
this.next = next;
}
public Node(T value, Node<T> next)
{
this.data = value;
this.next = next;
}
/// <summary>
/// 數(shù)據(jù) 存取器
/// </summary>
public T Data
{
get { return data; }
set { data = value; }
}
/// <summary>
/// 指針 存取器
/// </summary>
public Node<T> Next
{
get { return next; }
set { next = value; }
}
}
}
單鏈表
順序表是用地址連續(xù)的存儲(chǔ)單元順序存儲(chǔ)線性表中的各個(gè)元素,邏輯上相鄰你的數(shù)據(jù)元素在物理位置上也相鄰。因此,在線性表中查找任何一個(gè)位置上的數(shù)據(jù)元素非常方便,這是順序表存儲(chǔ)的優(yōu)點(diǎn)。但是,在對(duì)順序表進(jìn)行插入和刪除時(shí),需要通過移動(dòng)數(shù)據(jù)元素來實(shí)現(xiàn),影響了運(yùn)行效率。
線性表的另一種存儲(chǔ)結(jié)構(gòu)-鏈?zhǔn)酱鎯?chǔ)(Linked Storage),這樣的線性表叫鏈表(Linked List)。鏈表不要求邏輯的數(shù)據(jù)元素在物理存儲(chǔ)位置上也相鄰。因此,在對(duì)鏈表進(jìn)行插入和刪除時(shí),無需移動(dòng)數(shù)據(jù)元素,但同時(shí)也失去了順序表可隨機(jī)存儲(chǔ)的優(yōu)點(diǎn)。
namespace DataStructure
{
class SingleLinkedList<T> : IList<T>
{
/// <summary>
/// 頭節(jié)點(diǎn)
/// </summary>
private Node<T> head;
/// <summary>
/// 構(gòu)造器
/// </summary>
public SingleLinkedList()
{
head = null;
}
/// <summary>
/// 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get
{
Node<T> node = head;
//獲取當(dāng)前節(jié)點(diǎn)
for (int i = 0; i <= index; i++)
{
node = node.Next;
}
return node.Data;
}
}
/// <summary>
/// 單鏈表添加新節(jié)點(diǎn)
/// </summary>
/// <param name="item"></param>
public void Append(T item)
{
//創(chuàng)建新節(jié)點(diǎn)
Node<T> node = new Node<T>(item);
//判斷頭節(jié)點(diǎn)
if(head == null)
{
head = node;
}
else
{
//追加至尾節(jié)點(diǎn)
Node<T> tmp = head;
//獲取尾節(jié)點(diǎn)
while (true)
{
if (tmp.Next != null)
{
tmp = tmp.Next;
}
else
{
break;
}
}
//將新節(jié)點(diǎn)放入鏈表尾部
tmp.Next = node;
}
}
/// <summary>
/// 清空單鏈表
/// </summary>
public void Clear()
{
head = null;
}
/// <summary>
/// 獲取單鏈表長度
/// </summary>
/// <returns></returns>
public int Count()
{
//判斷頭節(jié)點(diǎn)是否為空
if (head == null)
{
return 0;
}
int count = 1;
Node<T> tmp = head;
while (true)
{
//若當(dāng)前節(jié)點(diǎn)存在下一個(gè)節(jié)點(diǎn)則長度自增1
if (tmp.Next != null)
{
count++;
tmp = tmp.Next;
}
else
{
break;
}
}
return count;
}
/// <summary>
/// 根據(jù)索引刪除單鏈表元素
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T Delete(int index)
{
T data = default(T);
//判斷是否為頭節(jié)點(diǎn)
if(index == 0)
{
data = head.Data;
head = head.Next;
}
else
{
Node<T> tmp = head;
//獲取當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
for(int i=1; i<=index - 1; i++)
{
tmp = tmp.Next;
}
Node<T> prevNode = tmp;
//獲取當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)
Node<T> currNode = tmp.Next;
data = currNode.Data;
//獲取當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
Node<T> nextNode = tmp.Next.Next;
//刪除當(dāng)前節(jié)點(diǎn)
prevNode.Next = nextNode;
}
return data;
}
/// <summary>
/// 根據(jù)指定索引獲取單鏈表數(shù)據(jù)
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T Get(int index)
{
return this[index];
}
/// <summary>
/// 指定位置插入新節(jié)點(diǎn)
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
public void Insert(T item, int index)
{
//目標(biāo)節(jié)點(diǎn)
Node<T> node = new Node<T>(item);
//插入位置為頭節(jié)點(diǎn)
if(index == 0)
{
node.Next = head;
head = node;
}
else
{
//目標(biāo)節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
Node<T> tmp = head;
//臨時(shí)節(jié)點(diǎn)后移index-1個(gè)位置
for (int i=1; i<=index - 1; i++)
{
tmp = tmp.Next;
}
Node<T> prevNode = tmp;
//獲取目標(biāo)節(jié)點(diǎn)
Node<T> currNode = tmp.Next;
//插入新節(jié)點(diǎn)
prevNode.Next = node;
node.Next = currNode;
}
}
/// <summary>
/// 判斷單鏈表是否為空
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return head == null;
}
/// <summary>
/// 根據(jù)數(shù)據(jù)獲取單鏈表的索引
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public int Location(T value)
{
Node<T> tmp = head;
if(tmp == null)
{
return -1;
}
else
{
int index = 0;
while (true)
{
if (tmp.Data.Equals(value))
{
return index;
}
else
{
if(tmp.Next != null)
{
tmp = tmp.Next;
}
else
{
break;
}
}
index++;
}
return -1;
}
}
}
}
雙向鏈表
單鏈表允許從一個(gè)結(jié)點(diǎn)直接訪問它的后繼節(jié)點(diǎn),所以,查找后繼結(jié)點(diǎn)的時(shí)間復(fù)雜度是O(1)。但是,要查找某個(gè)結(jié)點(diǎn)的直接前驅(qū)結(jié)點(diǎn),只能從表的頭引用開始遍歷各個(gè)結(jié)點(diǎn)。
如果某個(gè)結(jié)點(diǎn)的Next等于該結(jié)點(diǎn),那么,這個(gè)結(jié)點(diǎn)就是該結(jié)點(diǎn)的直接前驅(qū)結(jié)點(diǎn)。也就是說,查找直接前驅(qū)結(jié)點(diǎn)的時(shí)間復(fù)雜度是O(n),n是單鏈表的長度。當(dāng)然,我們也可以在結(jié)點(diǎn)的引用域內(nèi)保存直接前驅(qū)結(jié)點(diǎn)的地址而非直接后繼節(jié)點(diǎn)的地址。這樣,查找直接前驅(qū)結(jié)點(diǎn)的時(shí)間復(fù)雜度只有O(1),但查找后繼節(jié)點(diǎn)的時(shí)間復(fù)雜度是O(n)。
如果希望查找直接前驅(qū)結(jié)點(diǎn)和直接后繼節(jié)點(diǎn)的時(shí)間復(fù)雜度都是O(1),那么,需要在結(jié)點(diǎn)中設(shè)置兩個(gè)引用域,一個(gè)保存直接前驅(qū)結(jié)點(diǎn)的地址叫做prev,一個(gè)直接后繼節(jié)點(diǎn)的地址叫做next,這樣的鏈表就是雙向鏈表Doubly Linked List。


循環(huán)鏈表
某些應(yīng)用不需要鏈表中有明顯的頭尾結(jié)點(diǎn),在這種情況下,可能需要方便地從最后一個(gè)節(jié)點(diǎn)訪問到第一個(gè)節(jié)點(diǎn)。此時(shí),最后一個(gè)節(jié)點(diǎn)的引用域不是空引用,而是保存第一個(gè)結(jié)點(diǎn)的地址,如果該鏈表帶結(jié)點(diǎn)則保存的是頭結(jié)點(diǎn)的地址,也就是頭引用的值。
帶頭結(jié)點(diǎn)的循環(huán)鏈表(Circular Linked List)

棧和隊(duì)列
棧和隊(duì)列是非常重要的兩種數(shù)據(jù)結(jié)構(gòu),在軟件設(shè)計(jì)中應(yīng)用很多。棧和隊(duì)列也是線性結(jié)構(gòu),線性表、棧、隊(duì)列這三種數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)元素以及數(shù)據(jù)元素間的邏輯關(guān)系完全相同。差別在于線性表的操作不受限制,而棧和隊(duì)列的操作受到限制。棧的操作只能在表的一端進(jìn)行,隊(duì)列的插入操作在表的一端進(jìn)行而且其它操作在表的另一端進(jìn)行。所以,把棧和隊(duì)列稱為操作受限的線性表。
棧
棧(stack)是操作限定在表的尾端進(jìn)行的線性表,表尾由于要進(jìn)行插入、刪除等操作。所以,它具有特殊的含義,把表尾稱為棧頂(Top),另一端是固定的,叫做棧底(Bottom)。當(dāng)棧中沒有數(shù)據(jù)元素時(shí)叫做空棧(Empty Stack)。
棧通常標(biāo)記為:S = (a1, a2,... an),S是英文單詞Stack的第一個(gè)字母。a1為棧底元素,an為棧頂元素。這n個(gè)數(shù)據(jù)元素按照a1, a2...an的順序依次入棧,而出棧的次序相反。an第一個(gè)出棧,a1最后一個(gè)出棧。所以,棧的操作是按照后進(jìn)先出(LIFO, Last In First Out)或先進(jìn)后出(FILO, First In Last Out)的原則進(jìn)行的。因此,棧又稱為LIFO表或FILO表。
棧的操作示意圖
