.NET Core 資料結構與演算法 1-1
本節內容為順序表
簡介
線性表是簡單、基本、常用的資料結構。線性表是線性結構的抽象 (Abstract),線性結構的特點是結構中的資料元素之間存在一對一的線性關係。這 種一對一的關係指的是資料元素之間的位置關係,即:
- 除第一個位置的資料元素外,其它資料元素位置的前面都只有一個資料元素;
- 除後一個位置的資料元素外,其它資料元素位置的後面都只有一個元素。也就是說,資料元素是 一個接一個的排列。因此,可以把線性表想象為一種資料元素序列的資料結構。
本節我們對線性表中的順序表進行一個講解。
儲存線性表簡單、自然的方式,就是把表中的元素一個接一個地放進順序的儲存單元,這就是線性表的順序儲存(Sequence Storage)。線性表的順序儲存是指在記憶體中用一塊地址連續的空間依次存放線性表的資料元素, 用這種方式儲存的線性表叫順序表(Sequence List)。說的明確一些也就是說,順序表就是我們所接觸過的陣列。
線性表介面IListDS<T>
我們首先對我們會涉及到的函式進行一次封裝,打包進線性表的介面中
interface IListDS<T>
{
int GetLength();//求長度,我們也可以透過屬性進行操作
void Clear();//清空操作
bool IsEmpty();//判斷線性表是否為空
void Append(T item);//向後追加操作
void Insert(T item, int i);//插入操作
T Delete(int i);//刪除操作
T GetElem(int i);//取表元
}
對於C語言,我們很多需要使用函式進行操作,但是在C#中,我們有索引器,屬性等一系列語法糖,因此我們在後面操作的時候會把這些都展示給你們看。
事實上在我們之前的C#初級教程中的綜合練習,就是關於我們的線性表操作,你可以返回去參考你的程式碼。
順序表類SeqList<T>
為了方便起見,我們在此處不做可變長度的線性表,如果需要使用可變長度,這裡有那麼一種思路供讀者思考:定義一個欄位為容量(Cap),一個為長度(len),長度是以及儲存的空間大小,容量是總空間,如果長度和容量相等的時候,證明我們的表已經滿了,那麼就向後追加空的陣列即可。
不過在這裡我們不討論這種,我們僅僅使用定長的就足夠展示了
class SeqList<T>:IListDS<T>
{
private int length;//長度
private int size;
private int lastIndex;//最後一個元素的索引
private T[] data;
public int Length{get{return lastIndex+1;}}
//初始化
public SeqList<T>(int size)
{
this.data = new T[size];
this.lastIndex = -1;
this.size = size;
}
//清除內部元素
public void Clear()
{
this.data = new T[this.size];
lastIndex = -1;
}
//判斷是否為空,只需要判斷最後一個元素的索引是否為-1即可
public bool IsEmpty()
{
return lastIndex==-1?true:false;
}
//獲取長度
public int GetLength()
{
return lastIndex + 1;
}
//是否已滿
public bool IsFull()
{
return size == lastIndex+1?true:false;
}
//向後追加
public void Append(T item)
{
//只需要判斷是否已滿即可
if(!IsFull())
{
data[lastIndex++] = item;
}
else
{
Console.WriteLine("It is Full");
}
}
//在指定位置插入,index指代位置而不是索引
public void Insert(T item,int index)
{
//首先判斷是否已滿
if(IsFull())
{
Console.WriteLine("It is Full");
return;
}
if(index<1||index>lastIndex+2)
{
Console.WriteLine("error");
return;
}
//最後一位插入
if (i == last +2)
{
data[i-1] = item;
}
else
{
//元素移動
for (int j = lastIndex; j>= i-1; --j)
{
data[j + 1] = data[j];
}
data[i-1] = item;
}
lastIndex++;
}
public T Delete(int i)
{
T tmp = default(T);
//判斷表是否為空
if (IsEmpty())
{
Console.WriteLine("List is empty"); return tmp;
}
//判斷刪除的位置是否正確
// i小於1表示刪除第1個位置之前的元素,
// i大於last+1表示刪除後一個元素後面的第1個位置的元素。
if (i < 1 || i > lastIndex+1)
{
Console.WriteLine( "Position is error!");return tmp;
}
//刪除的是後一個元素
if (i == lastIndex+1)
{
tmp = data[last--];
}
//刪除的不是後一個元素
else
{
//元素移動
tmp = data[i-1];
for (int j = i; j <= lastIndex; ++j)
{
data[j] = data[j + 1];
}
}
//修改表長
--lastIndex;
return tmp;
}
public T GetElem(int i)
{
if(i<1||lastIndex==-1)
{
Console.WriteLine("error");
return;
}
return this.data[i-1];
}
}
在上述程式碼中,我們分析一下刪除和插入的操作
演算法的時間複雜度分析:順序表上的刪除操作與插入操作一樣,時間主要消 耗在資料的移動上在第i個位置刪除一個元素,從ai+1到an都要向前移動一個位置,共需要移動n-i個元素,而i的取值範圍為 1≤i≤n,當i等於 1 時,需要移動 的元素個數多,為n-1 個;當i為n時,不需要移動元素。設在第i個位置做刪除 的機率為pi,則平均移動資料元素的次數為(n-1)/2。這說明在順序表上做刪除操 作平均需要移動表中一半的資料元素,所以,刪除操作的時間複雜度為O(n)。
一些額外操作
我們以倒轉為例,事實上我們倒轉的時候,只需要將第一個和最後一個,第二個和倒數第二個以此類推進行交換即可
public void ReversSeqList(SeqList<int> L)
{
int tmp = 0;
int len = L.GetLength();
for (int i = 0; i<= len/2; ++i)
{
tmp = L[i];
L[i] = L[len - i];
L[len - i] = tmp;
}
}
各位可以嘗試一下生成不含重複值順序表和合並順序表並有序的排列演算法,這裡給出一些思路。
- 去重:先把順序表 La 的第 1 個元素賦給順序表 Lb,然後從順序表 La 的第 2 個元素起,每一個元素與順序表 Lb 中的每一個元素進行比較,如果不相 同,則把該元素附加到順序表 Lb 的末尾。
- 合併排序:依次掃描 La 和 Lb 的資料元素,比較 La 和 Lb 當前資料元素的 值,將較小值的資料元素賦給 Lc,如此直到一個順序表被掃描完,然後將未完 的那個順序表中餘下的資料元素賦給 Lc 即可。Lc 的容量要能夠容納 La 和 Lb 兩個表相加的長度。