c#資料結構學習總結
阿新 • • 發佈:2018-12-26
c#常用資料結構解析
http://blog.csdn.net/suifcd/article/details/42869341談談在平時使用U3D時經常用到的資料結構和各種資料結構的應用場景吧。
1.幾種常見的資料結構
這裡主要總結下小匹夫在工作中常碰到的幾種資料結構:Array,ArrayList,List<T>,LinkedList<T>,Queue<T>,Stack<T>,Dictionary<K,T>
陣列Array:
陣列是最簡單的資料結構。其具有如下特點:
陣列儲存在連續的記憶體上。
陣列的內容都是相同型別。
陣列可以直接通過下標訪問。
陣列Array的建立:
int size = 5;
int[] test = new int[size];
建立一個新的陣列時將在 CLR 託管堆中分配一塊連續的記憶體空間,來盛放數量為size,型別為所宣告型別的陣列元素。如果型別為值型別,則將會有size個未裝箱的該型別的值被建立。如果型別為引用型別,則將會有size個相應型別的引用被建立。
由於是在連續記憶體上儲存的,所以它的索引速度非常快,訪問一個元素的時間是恆定的也就是說與陣列的元素數量無關,而且賦值與修改元素也很簡單。
string[] test2 = new string[3];
//賦值
test2[0] = "chen";
test2[1] = "j";
test2[2] = "d";
//修改
test2[0] = "chenjd";
但是有優點,那麼就一定會伴隨著缺點。由於是連續儲存,所以在兩個元素之間插入新的元素就變得不方便。而且就像上面的程式碼所顯示的那樣,宣告一個新的陣列時,必須指定其長度,這就會存在一個潛在的問題,那就是當我們宣告的長度過長時,顯然會浪費記憶體,當我們宣告長度過短的時候,則面臨這溢位的風險。這就使得寫程式碼像是投機,小匹夫很厭惡這樣的行為!針對這種缺點,下面隆重推出ArrayList。
ArrayList:
為了解決陣列建立時必須指定長度以及只能存放相同型別的缺點而推出的資料結構。ArrayList是System.Collections名稱空間下的一部分,所以若要使用則必須引入System.Collections。正如上文所說,ArrayList解決了陣列的一些缺點。
不必在宣告ArrayList時指定它的長度,這是由於ArrayList物件的長度是按照其中儲存的資料來動態增長與縮減的。
ArrayList可以儲存不同型別的元素。這是由於ArrayList會把它的元素都當做Object來處理。因而,加入不同型別的元素是允許的。
ArrayList的操作:
ArrayList test3 = new ArrayList();
//新增資料
test3.Add("chen");
test3.Add("j");
test3.Add("d");
test3.Add("is");
test3.Add(25);
//修改資料
test3[4] = 26;
//刪除資料
test3.RemoveAt(4);
說了那麼一堆”優點“,也該說說缺點了吧。為什麼要給”優點”打上引號呢?那是因為ArrayList可以儲存不同型別資料的原因是由於把所有的型別都當做Object來做處理,也就是說ArrayList的元素其實都是Object型別的,辣麼問題就來了。
如上文所訴,陣列儲存值型別時並未發生裝箱,但是ArrayList由於把所有型別都當做了Object,所以不可避免的當插入值型別時會發生裝箱操作,在索引取值時會發生拆箱操作。這能忍嗎?
注:為何說頻繁的沒有必要的裝箱和拆箱不能忍呢?且聽小匹夫慢慢道來:所謂裝箱 (boxing):就是值型別例項到物件的轉換(百度百科)。那麼拆箱:就是將引用型別轉換為值型別咯(還是來自百度百科)。下面舉個栗子~
//裝箱,將String型別的值FanyoyChenjd賦值給物件。
String info = ”FanyoyChenjd”;
object obj=(object)info;
//拆箱,從Obj中提取值給info
object obj = "FanyoyChenjd";
String info = (String)obj;
那麼結論呢?顯然,從原理上可以看出,裝箱時,生成的是全新的引用物件,這會有時間損耗,也就是造成效率降低。
List<T>泛型List
為了解決ArrayList不安全型別與裝箱拆箱的缺點,所以出現了泛型的概念,作為一種新的陣列型別引入。也是工作中經常用到的陣列型別。和ArrayList很相似,長度都可以靈活的改變,最大的不同在於在宣告List集合時,我們同時需要為其宣告List集合內資料的物件型別,這點又和Array很相似,其實List<T>內部使用了Array來實現。
List<string> test4 = new List<string>();
//新增資料
test4.Add(“Fanyoy”);
test4.Add(“Chenjd”);
//修改資料
test4[1] = “murongxiaopifu”;
//移除資料
test4.RemoveAt(0);
這麼做最大的好處就是即確保了型別安全。也取消了裝箱和拆箱的操作。
它融合了Array可以快速訪問的優點以及ArrayList長度可以靈活變化的優點。
假設各位和小匹夫一樣,在工作中最常使用的一種資料結構就是它。那麼我們是否能再多一點好奇心呢?那就是探究一下,如果我們自己實現一個類似的資料結構,該從何處下手呢?
剛才說過了,List<T>的內部其實也是一個Array,且是強型別的,所以我們的簡單實現(暫且稱之為EggArray<T>)也秉承這個特點,內部通過一個Array來實現,且需要宣告型別。但是同時我們也看到List<T>繼承和實現了很多介面,比如IEnumerable介面等,而且值型別和引用型別通吃。這裡為了EggArray<T>實現起來輕裝簡行,我們不繼承List<T>繼承的各種介面,同時我們的EggArray只服務於引用型別。
那麼首先明確了,它是一個處理引用型別,且實現了泛型的。那麼定義就出來了:
//EggArray類
//定義
public
class
EggArray<T> where T : class
{
}
那麼下一步呢?該確定它的內部成員了,就先從欄位和屬性開始吧。
屬性&變數
屬性
說明
CapacityEggArray的容量
CountEggArray中的元素個數
itemsT[],一個Array,因為上一篇文章說過List<T>的內部其實還是Array,所以內部我們也使用Array
//EggArray<T>的屬性&&變數
private int capacity;
private int count;
private T[] items;
public int Count
{
get
{
return this.count;
}
}
public int Capacity
{
get
{
return this.capacity;
}
}
之後呢?好像是需要一個構造函數了。上文也說了,貌似new的時候不需要指定容量呀。那麼我們就把建構函式做成這樣吧。
建構函式:
建構函式說明
EggArray()初始化 EggArray<T> 類的新例項,該例項為空並且具有預設初始容量。
EggArray(int32)初始化 EggArray<T> 類的新例項,該例項為空並且具有指定的初始容量。
//EggArray的建構函式,預設容量為8
public
EggArray() : this(8)
{
}
public
EggArray(int
capacity)
{
this.capacity
= capacity;
this.items
= new
T[capacity];
}
好了,建構函式也說完了,那麼就介紹一下私有方法,因為執行機制全部是有私有方法來運籌的,公共方法只不過是開放給我們的使用的罷了。小匹夫對公共方法的實現沒有興趣,這裡就不做演示了。
剛剛也說了,List<T>是無所謂初始長度的,可以用Add()方法往裡面新增元素,同時也不可能是有一個無限大的空間讓它來儲存,那麼究竟它究竟為何能做到這一點呢?因為有一個能動態調整內部陣列大小的方法存在,且調整大小是按照原有長度成倍增長的。我們姑且稱之為Resize。
那麼在進行下面的內容之前,小匹夫還想先問各位一個問題:
List<int>
test = new
List<int>(){0,1,2,3,4,5,6,7,8,9};
int
count = 0;
for(int
i = 0; i < test.Count; i++)
{
if(i == 1)
test.Remove(test[i]);
count++;
}
Debug.Log (count);
上面這段程式碼會輸出什麼呢?答案是9。可能有的盆油會感到奇怪,test進去時長度明明是10啊。就算你中間Remove了一個元素,可為什麼會影響後面的元素呢?(比如把index為1的元素remove掉,原來index為2的元素現在的index就成1了。)感覺亂套有木有?其實這裡List<T>在執行remove的同時,也把內部的陣列壓縮了。所以也肯定有一個方法用來壓縮咯。我們姑且稱為Compact。
私有方法
私有方法
說明
Resize當陣列元素個數大於或等於陣列的容量時,呼叫該方法進行擴容,會建立一個新的Array存放資料,“增長因子”為2
Compact壓縮陣列,在Remove時候預設呼叫
//當陣列元素個[/size][/backcolor][/color][i][color=White][backcolor=DarkGreen][size=2]數不小於陣列容量時,需要擴容,增長因子growthFactor為2
private
void
Resize()
{
int
capacity = this.capacity
* growthFactor;
if
(this.count
> capacity)
{
this.count
= capacity;
}
T[]
destinationArray = new
T[capacity];
Array.Copy(this.items,
destinationArray, this.count);
this.items
= destinationArray;
this.capacity
= capacity;
}
private
void
Compact()
{
int
num = 0;
for
(int
i = 0; i < this.count;
i++)
{
if
(this.items[i]
== null)
{
num++;
}
else
if
(num > 0)
{
this.items[i
- num] = this.items[i];
this.items[i]
= null;
}
}
this.count
-= num;
}[i][i][i]
LinkedList<T>
也就是連結串列了。和上述的陣列最大的不同之處就是在於連結串列在記憶體儲存的排序上可能是不連續的。這是由於連結串列是通過上一個元素指向下一個元素來排列的,所以可能不能通過下標來訪問。如圖
既然連結串列最大的特點就是儲存在記憶體的空間不一定連續,那麼連結串列相對於陣列最大優勢和劣勢就顯而易見了。
向連結串列中插入或刪除節點無需調整結構的容量。因為本身不是連續儲存而是靠各物件的指標所決定,所以新增元素和刪除元素都要比陣列要有優勢。
連結串列適合在需要有序的排序的情境下增加新的元素,這裡還拿陣列做對比,例如要在陣列中間某個位置增加新的元素,則可能需要移動移動很多元素,而對於連結串列而言可能只是若干元素的指向發生變化而已。
有優點就有缺點,由於其在記憶體空間中不一定是連續排列,所以訪問時候無法利用下標,而是必須從頭結點開始,逐次遍歷下一個節點直到尋找到目標。所以當需要快速訪問物件時,陣列無疑更有優勢。
綜上,連結串列適合元素數量不固定,需要兩端存取且經常增減節點的情況。
關於連結串列的使用,MSDN上有詳細的例子。
Queue<T>
在Queue<T>這種資料結構中,最先插入在元素將是最先被刪除;反之最後插入的元素將最後被刪除,因此佇列又稱為“先進先出”(FIFO—first in first out)的線性表。通過使用Enqueue和Dequeue這兩個方法來實現對 Queue<T> 的存取。
一些需要注意的地方:
先進先出的情景。
預設情況下,Queue<T>的初始容量為32, 增長因子為2.0。
當使用Enqueue時,會判斷佇列的長度是否足夠,若不足,則依據增長因子來增加容量,例如當為初始的2.0時,則佇列容量增長2倍。
乏善可陳。
關於Queue<T>的使用方法,MSDN上也有相應的例子。
Stack<T>
與Queue<T>相對,當需要使用後進先出順序(LIFO)的資料結構時,我們就需要用到Stack<T>了。
一些需要注意的地方:
後進先出的情景。
預設容量為10。
使用pop和push來操作。
乏善可陳。
同樣,在MSDN你也可以看到大量Stack<T>的例子。
Dictionary<K,T>
字典這東西,小匹夫可是喜歡的不得了。看官們自己也可以想想字典是不是很招人喜歡,建立一個字典之後就可以往裡面扔東西,增加、刪除、訪問那叫一個快字了得。但是直到小匹夫日前看了一個大神的文章,才又想起了那句話“啥好事咋能讓你都佔了呢”。那麼字典背後到底隱藏著什麼迷霧,撥開重重迷霧之後,是否才是真相?且聽下回分。。。等等,應該是下面就讓我們來分析一下字典吧。
提到字典就不得不說Hashtable雜湊表以及Hashing(雜湊,也有叫雜湊的),因為字典的實現方式就是雜湊表的實現方式,只不過字典是型別安全的,也就是說當建立字典時,必須宣告key和item的型別,這是第一條字典與雜湊表的區別。關於雜湊表的內容推薦看下這篇部落格雜湊表。關於雜湊,簡單的說就是一種將任意長度的訊息壓縮到某一固定長度,比如某學校的學生學號範圍從00000~99999,總共5位數字,若每個數字都對應一個索引的話,那麼就是100000個索引,但是如果我們使用後3位作為索引,那麼索引的範圍就變成了000~999了,當然會衝突的情況,這種情況就是雜湊衝突(Hash Collisions)了。扯遠了,關於具體的實現原理還是去看小匹夫推薦的那篇部落格吧,當然那篇部落格上面那個大大的轉字也是蠻刺眼的。。。
回到Dictionary<K,T>,我們在對字典的操作中各種時間上的優勢都享受到了,那麼它的劣勢到底在哪呢?對嘞,就是空間。以空間換時間,通過更多的記憶體開銷來滿足我們對速度的追求。在建立字典時,我們可以傳入一個容量值,但實際使用的容量並非該值。而是使用“不小於該值的最小質數來作為它使用的實際容量,最小是3。”(老趙),當有了實際容量之後,並非直接實現索引,而是通過建立額外的2個數組來實現間接的索引,即int[] buckets和Entry[] entries兩個陣列(即buckets中儲存的其實是entries陣列的下標),這裡就是第二條字典與雜湊表的區別,還記得雜湊衝突嗎?對,第二個區別就是處理雜湊衝突的策略是不同的!字典會採用額外的資料結構來處理雜湊衝突,這就是剛才提到的陣列之一buckets桶了,buckets的長度就是字典的真實長度,因為buckets就是字典每個位置的對映,然後buckets中的每個元素都是一個連結串列,用來儲存相同雜湊的元素,然後再分配儲存空間。
因此,我們面臨的情況就是,即便我們新建了一個空的字典,那麼伴隨而來的是2個長度為3的陣列。所以當處理的資料不多時,還是慎重使用字典為好,很多情況下使用陣列也是可以接受的。
2.幾種常見資料結構的使用情景
Array需要處理的元素數量確定並且需要使用下標時可以考慮,不過建議使用List<T>
ArrayList不推薦使用,建議用List<T>
List<T>泛型List需要處理的元素數量不確定時 通常建議使用
LinkedList<T>連結串列適合元素數量不固定,需要經常增減節點的情況,2端都可以增減
Queue<T>先進先出的情況
Stack<T>後進先出的情況
Dictionary<K,T>需要鍵值對,快速操作
========
C#資料結構一:基礎知識
在學習資料結構之前先要學習幾個相關的概念及術語1、資料(Data):資料是外部世界資訊的載體,它能被計算機識別、儲存和加工處理,是計算機程式加工的原料。2、資料元素(Data Element)和資料項:資料元素是資料的基本單位,有時也被稱為元素、結點、頂點、記錄等。一個數據元素可由若干個資料項組成;資料項是不可分割的、含有獨立意義的最小資料單位,資料項有時也稱為欄位(Field)或域(Domain).之間關係為資料項組成資料元素,資料元素組成資料(,資料組成檔案)。使用資料庫模型來舉例說明:3、資料物件(Data Object):性質相同的資料元素的集合,是資料的一個子集,例如字母表物件{a,b,c,…x,y,z}4、資料型別(Data Type):資料的取值範圍和對資料進行操作的總和。資料型別規定了程式中物件的特性;程式中每個變數、常量或表示式的結果都應該屬於某種確定的資料型別。資料型別可分可兩類:一類是非結構的原子型別,如C#的基本型別;另一類是結構型別,其成分由多個結構型別組成,可以分解;如C#的陣列型別。5、資料結構(Data Struct):相互之間存在一種或多種關係 的資料元素的集合。通常有4類基本資料結構:1)集合(Set)2)線性結構(Linear Structure)3)樹形結構(True Structure)4)圖狀結構(Graphic Structure)資料結構(Data Structrue)簡記為DS,是一個二元組,DS=(D,S),其中D為資料元素的有限集合,R是資料元素之間關係的有限集合。6、演算法(Algorithm):是對某一特定型別的問題的求解步驟的一種描述,是指令的有限序列。它具有有窮性(Finity)、確定性(Unambiguousness)、輸入(Input)、輸出(Output)和有效性(Realizability)。針對演算法優劣的評價標準包括正確性(Correctness)、可讀性(Readability)、健壯性(Robustness魯棒性)、執行時間(Running Time)和佔用空間(Storage Space)。7、演算法的時間複雜度(Time Complexity):指演算法的執行時間與問題規模的對應關係。通常把演算法中基本操作重複執行的次數作為演算法的時間複雜度。它是與問題規模n相關的函式。記作T(n)=O(f(n)),例如T(n)=n(n+1),推薦一篇好文http://www.matrix67.com/blog/archives/5298、高等數學相關基礎知識計量單位(Unit):位元組為B,位縮寫為b,兆位元組為MB,千位元組縮寫為KB階乘函式(Factorial Function):5!=5*4*3*2*1=120,特別地,0!=1取下整和取上整(Floor and Ceiling):⌊3.4⌋=3(下整) ,⌈3.4⌉=4(上整)取模操作符(Modulus):n=q*m+r ⇒m=n/q對數(Logarithm):若ab=N,那麼數b叫做以a為底N的對數,記作logaN=b,其中a叫做對數的底數,N叫做真數。遞迴(Recursive):演算法呼叫自己或間接呼叫自己。
在學習資料結構之前先要學習幾個相關的概念及術語
1、資料(Data):資料是外部世界資訊的載體,它能被計算機識別、儲存和加工處理,是計算機程式加工的原料。
2、資料元素(Data Element)和資料項:資料元素是資料的基本單位,有時也被稱為元素、結點、頂點、記錄等。一個數據元素可由若干個資料項組成;資料項是不可分割的、含有獨立意義的最小資料單位,資料項有時也稱為欄位(Field)或域(Domain).之間關係為資料項組成資料元素,資料元素組成資料(,資料組成檔案)。使用資料庫模型來舉例說明:
3、資料物件(Data Object):性質相同的資料元素的集合,是資料的一個子集,例如字母表物件{a,b,c,…x,y,z}
4、資料型別(Data Type):資料的取值範圍和對資料進行操作的總和。資料型別規定了程式中物件的特性;程式中每個變數、常量或表示式的結果都應該屬於某種確定的資料型別。資料型別可分可兩類:一類是非結構的原子型別,如C#的基本型別;另一類是結構型別,其成分由多個結構型別組成,可以分解;如C#的陣列型別
。5、資料結構(Data Struct):相互之間存在一種或多種關係 的資料元素的集合。通常有4類基本資料結構:
1)集合(Set)
2)線性結構(Linear Structure)
3)樹形結構(True Structure)
4)圖狀結構(Graphic Structure)
資料結構(Data Structrue)簡記為DS,是一個二元組,DS=(D,S),其中D為資料元素的有限集合,R是資料元素之間關係的有限集合。
6、演算法(Algorithm):是對某一特定型別的問題的求解步驟的一種描述,是指令的有限序列。它具有有窮性(Finity)、確定性(Unambiguousness)、輸入(Input)、輸出(Output)和有效性(Realizability)。針對演算法優劣的評價標準包括正確性(Correctness)、可讀性(Readability)、健壯性(Robustness魯棒性)、執行時間(Running Time)和佔用空間(Storage Space)。
7、演算法的時間複雜度(Time Complexity):指演算法的執行時間與問題規模的對應關係。通常把演算法中基本操作重複執行的次數作為演算法的時間複雜度。它是與問題規模n相關的函式。記作T(n)=O(f(n)),例如T(n)=n(n+1)。
常見時間複雜度舉例:
1)、O(n)
x=n;
y=0;
while(y<x){
y=y+1;
}
2)、O(n2)
for(int i=1;i<n;++i){
for(int j=0;j<n;++j){
A[i][j]=i*j;
}
}
3)、O(\sqrt{n})
x=n;
y=0;
while(x>=(y+1)*(y+1)){//即x=y2+1
y=y+1;
}
關於演算法複雜度,推薦一篇好文http://www.matrix67.com/blog/archives/529
8、高等數學相關基礎知識
計量單位(Unit):位元組為B,位縮寫為b,兆位元組為MB,千位元組縮寫為KB
階乘函式(Factorial Function):5!=5*4*3*2*1=120,特別地,0!=1
取下整和取上整(Floor and Ceiling):⌊3.4⌋=3(下整) ,⌈3.4⌉=4(上整)
取模操作符(Modulus):n=q*m+r ⇒m=n/q
對數(Logarithm):若ab=N,那麼數b叫做以a為底N的對數,記作logaN=b,其中a叫做對數的底數,N叫做真數。
遞迴(Recursive):演算法呼叫自己或間接呼叫自己。
C#資料結構系列文章:
1、基礎知識
2、順序表Sequence List
3、單鏈表Singly Linked List
4、雙向連結串列Double Linked List
5、迴圈連結串列Circular Linked List
6、棧Stack
7、佇列Queue
8、串
9、陣列Array
10、樹Tree
========
C# list使用方法
集合是OOP中的一個重要概念,C#中對集合的全面支援更是該語言的精華之一。
為什麼要用泛型集合?
在C# 2.0之前,主要可以通過兩種方式實現集合:
a.使用ArrayList
直接將物件放入ArrayList,操作直觀,但由於集合中的項是Object型別,因此每次使用都必須進行繁瑣的型別轉換。
b.使用自定義集合類
比較常見的做法是從CollectionBase抽象類繼承一個自定義類,通過對IList物件進行封裝實現強型別集合。這種方式要求為每種集合型別寫一個相應的自定義類,工作量較大。泛型集合的出現較好的解決了上述問題,只需一行程式碼便能建立指定型別的集合。
什麼是泛型?
泛型是C# 2.0中的新增元素(C++中稱為模板),主要用於解決一系列類似的問題。這種機制允許將類名作為引數傳遞給泛型型別,並生成相應的物件。將泛型(包括類、介面、方法、委託等)看作模板可能更好理解,模板中的變體部分將被作為引數傳進來的類名稱所代替,從而得到一個新的型別定義。泛型是一個比較大的話題,在此不作詳細解析,有興趣者可以查閱相關資料。
怎樣建立泛型集合?
主要利用System.Collections.Generic名稱空間下面的List<T>泛型類建立集合,語法如下:
定義Person類如下:
可以看到,泛型集合大大簡化了集合的實現程式碼,通過它,可以輕鬆建立指定型別的集合。非但如此,泛型集合還提供了更加強大的功能,下面看看其中的排序及搜尋。
List<T> ListOfT = new List<T>();
其中的"T"就是所要使用的型別,既可以是簡單型別,如string、int,也可以是使用者自定義型別。下面看一個具體例子。
class Person
{
private string _name; //姓名
private int _age; //年齡
//建立Person物件
public Person(string Name, int Age)
{
this._name= Name;
this._age = Age;
}
//姓名
public string Name
{
get { return _name; }
}
//年齡
public int Age
{
get { return _age; }
}
}
//建立Person物件
Person p1 = new Person("張三", 30);
Person p2 = new Person("李四", 20);
Person p3 = new Person("王五", 50);
//建立型別為Person的物件集合
List<Person> persons = new List<Person>();
//將Person物件放入集合
persons.Add(p1);
persons.Add(p2);
persons.Add(p3);
//輸出第2個人的姓名
Console.Write(persons[1].Name);
泛型集合的排序
排序基於比較,要排序,首先要比較。比如有兩個數1、2,要對他們排序,首先就要比較這兩個數,根據比較結果來排序。如果要比較的是物件,情況就要複雜一點,比如對Person物件進行比較,則既可以按姓名進行比較,也可以按年齡進行比較,這就需要確定比較規則。一個物件可以有多個比較規則,但只能有一個預設規則,預設規則放在定義該物件的類中。預設比較規則在CompareTo方法中定義,該方法屬於IComparable<T>泛型介面。請看下面的程式碼:
class Person :IComparable<Person>
{
//按年齡比較
public int CompareTo(Person p)
{
return this.Age - p.Age;
}
}
CompareTo方法的引數為要與之進行比較的另一個同類型物件,返回值為int型別,如果返回值大於0,表示第一個物件大於第二個物件,如果返回值小於0,表示第一個物件小於第二個物件,如果返回0,則兩個物件相等。
定義好預設比較規則後,就可以通過不帶引數的Sort方法對集合進行排序,如下所示:
//按照預設規則對集合進行排序
persons.Sort();
//輸出所有人姓名
foreach (Person p in persons)
{
Console.WriteLine(p.Name); //輸出次序為"李四"、"張三"、"王五"
}
實際使用中,經常需要對集合按照多種不同規則進行排序,這就需要定義其他比較規則,可以在Compare方法中定義,該方法屬於IComparer<T>泛型介面,請看下面的程式碼:
class NameComparer : IComparer<Person>
{
//存放排序器例項
public static NameComparer Default = new NameComparer();
//按姓名比較
public int Compare(Person p1, Person p2)
{
return System.Collections.Comparer.Default.Compare(p1.Name, p2.Name);
}
}
Compare方法的引數為要進行比較的兩個同類型物件,返回值為int型別,返回值處理規則與CompareTo方法相同。其中的Comparer.Default返回一個內建的Comparer物件,用於比較兩個同類型物件。
下面用新定義的這個比較器對集合進行排序:
還可以通過委託來進行集合排序,首先要定義一個供委託呼叫的方法,用於存放比較規則,可以用靜態方法。請看下面的程式碼:然後通過內建的泛型委託System.Comparison<T>對集合進行排序:
可以看到,後兩種方式都可以對集合按照指定規則進行排序,但筆者更偏向於使用委託方式,可以考慮把各種比較規則放在一個類中,然後進行靈活呼叫。
//按照姓名對集合進行排序
persons.Sort(NameComparer.Default);
//輸出所有人姓名
foreach (Person p in persons)
{
Console.WriteLine(p.Name); //輸出次序為"李四"、"王五"、"張三"
}class PersonComparison
{
//按姓名比較
public static int Name(Person p1, Person p2)
{
return System.Collections.Comparer.Default.Compare(p1.Name, p2.Name);
}
}
方法的引數為要進行比較的兩個同類型物件,返回值為int型別,返回值處理規則與CompareTo方法相同。
System.Comparison<Person> NameComparison = new System.Comparison<Person>(PersonComparison.Name);
persons.Sort(NameComparison);
//輸出所有人姓名
foreach (Person p in persons)
{
Console.WriteLine(p.Name); //輸出次序為"李四"、"王五"、"張三"
}
可以看到,後兩種方式都可以對集合按照指定規則進行排序,但筆者更偏向於使用委託方式,可以考慮把各種比較規則放在一個類中,然後進行靈活呼叫。
泛型集合的搜尋
搜尋就是從集合中找出滿足特定條件的項,可以定義多個搜尋條件,並根據需要進行呼叫。首先,定義搜尋條件,如下所示:
class PersonPredicate
{
//找出中年人(40歲以上)
public static bool MidAge(Person p)
{
if (p.Age >= 40)
return true;
else
return false;
}
}
上面的搜尋條件放在一個靜態方法中,方法的返回型別為布林型,集合中滿足特定條件的項返回true,否則返回false。
System.Predicate<Person> MidAgePredicate = new System.Predicate<Person>(PersonPredicate.MidAge);
List<Person> MidAgePersons = persons.FindAll(MidAgePredicate);
//輸出所有的中年人姓名
foreach (Person p in MidAgePersons)
{
Console.WriteLine(p.Name); //輸出"王五"
}然後通過內建的泛型委託System.Predicate<T>對集合進行搜尋:
泛型集合的擴充套件
如果要得到集合中所有人的姓名,中間以逗號隔開,那該怎麼處理?
考慮到單個類可以提供的功能是有限的,很自然會想到對List<T>類進行擴充套件,泛型類也是類,因此可以通過繼承來進行擴充套件。請看下面的程式碼:
//定義Persons集合類
class Persons : List<Person>
{
//取得集合中所有人姓名
public string GetAllNames()
{
if (this.Count == 0)
return "";
string val = "";
foreach (Person p in this)
{
val += p.Name + ",";
}
return val.Substring(0, val.Length - 1);
}
}
//建立並填充Persons集合
Persons PersonCol = new Persons();
PersonCol.Add(p1);
PersonCol.Add(p2);
PersonCol.Add(p3);
//輸出所有人姓名
Console.Write(PersonCol.GetAllNames()); //輸出“張三,李四,王五”
List的方法和屬性 方法或屬性 作用
Capacity 用於獲取或設定List可容納元素的數量。當數量超過容量時,這個值會自動增長。您可以設定這個值以減少容量,也可以呼叫trin()方法來減少容量以適合實際的元素數目。
Count 屬性,用於獲取陣列中當前元素數量
Item( ) 通過指定索引獲取或設定元素。對於List類來說,它是一個索引器。
Add( ) 在List中新增一個物件的公有方法
AddRange( ) 公有方法,在List尾部新增實現了ICollection介面的多個元素
BinarySearch( ) 過載的公有方法,用於在排序的List內使用二分查詢來定位指定元素.
Clear( ) 在List內移除所有元素
Contains( ) 測試一個元素是否在List內
CopyTo( ) 過載的公有方法,把一個List拷貝到一維陣列內
Exists( ) 測試一個元素是否在List內
Find( ) 查詢並返回List內的出現的第一個匹配元素
FindAll( ) 查詢並返回List內的所有匹配元素
GetEnumerator( ) 過載的公有方法,返回一個用於迭代List的列舉器
Getrange( ) 拷貝指定範圍的元素到新的List內
IndexOf( ) 過載的公有方法,查詢並返回每一個匹配元素的索引
Insert( ) 在List內插入一個元素
InsertRange( ) 在List內插入一組元素
LastIndexOf( ) 過載的公有方法,,查詢並返回最後一個匹配元素的索引
Remove( ) 移除與指定元素匹配的第一個元素
RemoveAt( ) 移除指定索引的元素
RemoveRange( ) 移除指定範圍的元素
Reverse( ) 反轉List內元素的順序
Sort( ) 對List內的元素進行排序
ToArray( ) 把List內的元素拷貝到一個新的陣列內
trimToSize( ) 將容量設定為List中元素的實際數目
小結:
本文著重於介紹運用C# 2.0中的泛型來實現集合,以及對集合功能進行擴充套件,恰當的運用泛型集合,可以減少很多重複工作,極大的提高開發效率。實際上,集合只不過是泛型的一個典型應用,如果想了解更多關於泛型的知識,可以查閱其他相關資料。希望本文對你有用:
========
C# List 用法
http://www.blogjava.net/ebecket/articles/301842.htmlC# List Examples
by Sam Allen - Updated September 6, 2009
Problem. You have questions about the List collection in the .NET Framework, which is located in the System.Collections.Generic namespace. You want to see examples of using List and also explore some of the many useful methods it provides, making it an ideal type for dynamically adding data. Solution. This document has lots of tips and resources on the List constructed type, with examples using the C# programming language.
--- Key points: ---
Lists are dynamic arrays in the C# language.
They can grow as needed when you add elements.
They are called generic collections and constructed types.
You need to use < and > in the List declaration.
1. Adding values
Here we see how to declare a new List of int values and add integers to it. This example shows how you can create a new List of unspecified size, and add four prime numbers to it. Importantly, the angle brackets are part of the declaration type, not conditional operators that mean less or more than. They are treated differently in the language.
~~~ Program that adds elements to List (C#) ~~~
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> list = new List<int>();
list.Add(2);
list.Add(3);
list.Add(5);
list.Add(7);
}
}
Adding objects. The above example shows how you can add a primitive type such as integer to a List collection, but the List collection can receive reference types and object instances. There is more information on adding objects with the Add method on this site. [C# List Add Method - dotnetperls.com]
2. Loops
Here we see how you can loop through your List with for and foreach loops. This is a very common operation when using List. The syntax is the same as that for an array, except your use Count, not Length for the upper bound. You can also loop backwards through your List by reversing the for loop iteration variables. Start with list.Count - 1, and proceed decrementing to >= 0.
~~~ Program that loops through List (C#) ~~~
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> list = new List<int>();
list.Add(2);
list.Add(3);
list.Add(7);
foreach (int prime in list) // Loop through List with foreach
{
Console.WriteLine(prime);
}
for (int i = 0; i < list.Count; i++) // Loop through List with for
{
Console.WriteLine(list[i]);
}
}
}
~~~ Output of the program ~~~
(Repeated twice)
2
3
7
3. Counting elements
To get the number of elements in your List, access the Count property. This is fast to access, if you avoid the Count() extension method. Count is equal to Length on arrays. See the section "Clearing List" for an example on using the Count property.
4. Clearing List—setting to null
Here we see how to use the Clear method, along with the Count property, to erase all the elements in your List. Before Clear is called, this List has 3 elements; after Clear is called, it has 0 elements. Alternatively, you can assign the List to null instead of calling Clear, with similar performance. However, after assigning to null, you must call the constructor again.
=== Program that counts List (C#) ===
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<bool> list = new List<bool>();
list.Add(true);
list.Add(false);
list.Add(true);
Console.WriteLine(list.Count); // 3
list.Clear();
Console.WriteLine(list.Count); // 0
}
}
=== Output of the program ===
3
0
5. Copying array to List
Here we see an easy way to create a new List with the elements in an array that already exists. You can use the List constructor and pass it the array as the parameter. List receives this parameter, and fills its values from it.
--- Program that copies array to List (C#) ---
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
int[] arr = new int[3]; // New array with 3 elements
arr[0] = 2;
arr[1] = 3;
arr[2] = 5;
List<int> list = new List<int>(arr); // Copy to List
Console.WriteLine(list.Count); // 3 elements in List
}
}
--- Output of the program ---
Indicates number of elements.
3
Notes on the example. It is useful to use the List constructor code here to create a new List from Dictionary keys. This will give you a List of the Dictionary keys. The array element type must match the type of the List elements, or the compiler will refuse to compile your code.
6. Finding elements
Here we an example of how you can test each element in your List for a certain value. This shows the foreach loop, which tests to see if 3 is in the List of prime numbers. Note that more advanced List methods are available to find matches in the List, but they often aren't any better than this loop. They can sometimes result in shorter code. [C# List Find Methods for Searching List - dotnetperls.com]
~~~ Program that uses foreach on List (C#) ~~~
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// New list for example
List<int> primes = new List<int>(new int[] { 2, 3, 5 });
// See if List contains 3
foreach (int number in primes)
{
if (number == 3) // Will match once
{
Console.WriteLine("Contains 3");
}
}
}
}
~~~ Output of the program ~~~
Contains 3
7. Using capacity
You can use the Capacity property on List, or pass an integer into the constructor, to improve allocation performance when using List. The author's research shows that capacity can improve performance by nearly 2x for adding elements. Note however that this is not usually a performance bottleneck in programs that access data. [C# Capacity Property - dotnetperls.com]
TrimExcess method. There is the TrimExcess method on List as well, but its usage is very limited and I have never needed to use it. It reduces the memory used. Note: "The TrimExcess method does nothing if the list is at more than 90 percent of capacity". [List(T).TrimExcess Method - MSDN]
8. Using BinarySearch
You can use the binary search algorithm on List with the instance BinarySearch method. Binary search uses guesses to find the correct element much faster than linear searching. It is often much slower than Dictionary. [C# BinarySearch List - dotnetperls.com]
9. Using AddRange and InsertRange
You can use AddRange and InsertRange to add or insert collections of elements into your existing List. This can make your code simpler. See an example of these methods on this site. [C# List AddRange Use - dotnetperls.com]
10. Using ForEach method
Sometimes you may not want to write a regular foreach loop, which makes ForEach useful. This accepts an Action, which is a void delegate method. Be very cautious when you use Predicates and Actions, because they can decrease the readability of your code.
Another useful method. There is a TrueForAll method that accepts a Predicate. If the Predicate returns true for each element in your List, the TrueForAll method will return true also. Else, it will return false.
11. Using Join—string List
Here we see how you can use string.Join on a List of strings. This is useful when you need to turn several strings into one comma-delimited string. It requires the ToArray instance method on List. The biggest advantage of Join here is that no trailing comma is present on the resulting string, which would be present in a loop where each string is appended.
=== Program that joins List (C#) ===
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// List of cities we need to join
List<string> cities = new List<string>();
cities.Add("New York");
cities.Add("Mumbai");
cities.Add("Berlin");
cities.Add("Istanbul");
// Join strings into one CSV line
string line = string.Join(",", cities.ToArray());
Console.WriteLine(line);
}
}
=== Output of the program ===
New York,Mumbai,Berlin,Istanbul
12. Getting List from Keys in Dictionary
Here we see how you can use the List constructor to get a List of keys in your Dictionary collection. This gives you a simple way to iterate over Dictionary keys, or store them elsewhere. The Keys instance property accessor on Dictionary returns an enumerable collection of keys, which can be passed to the List constructor as a parameter.
::: Program that converts Keys (C#) :::
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Populate example Dictionary
var dict = new Dictionary<int, bool>();
dict.Add(3, true);
dict.Add(5, false);
// Get a List of all the Keys
List<int> keys = new List<int>(dict.Keys);
foreach (int key in keys)
{
Console.WriteLine(key);
}
}
}
::: Output of the program :::
3, 5
13. Inserting elements
Here we see how you can insert an element into your List at any position. The string "dalmation" is inserted into index 1, which makes it become the second element in the List. Note that if you have to Insert elements extensively, you should consider the Queue and LinkedList collections for better performance. Additionally, a Queue may provide clearer usage of the collection in your code.
~~~ Program that inserts into List (C#) ~~~
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> dogs = new List<string>(); // Example List
dogs.Add("spaniel"); // Contains: spaniel
dogs.Add("beagle"); // Contains: spaniel, beagle
dogs.Insert(1, "dalmation"); // Contains: spaniel, dalmation, beagle
foreach (string dog in dogs) // Display for verification
{
Console.WriteLine(dog);
}
}
}
~~~ Output of the program ~~~
spaniel
dalmation
beagle
14. Removing elements
The removal methods on List are covered in depth in another article on this site. It contains examples for Remove, RemoveAt, RemoveAll, and RemoveRange, along with the author's notes. [C# List Remove Methods - dotnetperls.com]
15. Sorting and reversing
You can use the powerful Sort and Reverse methods in your List collection. These allow you to order your List in ascending or descending order. Additionally, you can use Reverse even when your List is not presorted. There is more information on these topics, as well as sorting your List with LINQ on a property on this site. [C# Sort List Method, Sorting and Reversing Lists - dotnetperls.com]
16. Converting List to array
You can convert your List to an array of the same type using the instance method ToArray. There are examples of this conversion, and the opposite, on this site. [C# Convert List to Array - dotnetperls.com]
17. Getting range of elements
Here we see how you can get a range of elements in your List collection using the GetRange instance method. This is similar to the Take and Skip methods from LINQ, but has different syntax.
--- Program that gets ranges from List (C#) ---
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> rivers = new List<string>(new string[]
{
"nile",
"amazon", // River 2
"yangtze", // River 3
"mississippi",
"yellow"
});
// Get rivers 2 through 3
List<string> range = rivers.GetRange(1, 2);
foreach (string river in range)
{
Console.WriteLine(river);
}
}
}
--- Output of the program ---
amazon
yangtze
18. Testing Lists for equality
Sometimes you may need to test two Lists for equality, even when their elements are unordered. You can do this by sorting both of them and then comparing, or by using a custom List equality method. This site contains an example of a method that tests lists for equality in an unordered way. [C# List Element Equality - dotnetperls.com]
19. Using List with structs
When using List, you can improve performance and reduce memory usage with structs instead of classes. A List of structs is allocated in contiguous memory, unlike a List of classes. This is an advanced optimization. Note that in many cases using structs will actually decrease the performance when they are used as parameters in methods such as those on the List type.
20. Using var keyword
Here we see how you can use List collections with the var keyword. This can greatly shorten your lines of code, which sometimes improves readability. The var keyword has no effect on performance, only readability for programmers.
~~~ Program that uses var with List (C#) ~~~
using System.Collections.Generic;
class Program
{
static void Main()
{
var list1 = new List<int>(); // <- var keyword used
List<int> list2 = new List<int>(); // <- Is equivalent to
}
}
21. Summary
Here we saw lots of examples with the List constructed type. You will find that List is powerful and performs well. It provides flexible allocation and growth, making it much easier to use than arrays. In most programs that do not have memory or performance constraints and must add elements dynamically, the List constructed type in the C# programming language is ideal.
List 類是 ArrayList 類的泛型等效類,某些情況下,用它比用陣列和 ArrayList 都方便。
我們假設有一組資料,其中每一項資料都是一個結構。
public struct Item
{
public int Id;
public string DisplayText;
}
注意結構是不能給例項欄位賦值的,即 public int Id = 1 是錯誤的。
using System.Collections.Generic;
List<Item> items = new List<Item>();
//新增
Item item1 = new Item();
item1.Id = 0;
item1.DisplayText = "水星";
items.Add(item1);
//新增
Item item2 = new Item();
item2.Id = 1;
item2.DisplayText = "地球";
items.Add(item2);
//修改
//這裡使用的是結構,故不能直接用 items[1].DisplayText = "金星";,如果 Item 是類,則可以直接用。為什麼呢?因為結構是按值傳遞的。
Item item = items[1];
item.DisplayText = "金星";
items[1] = item;
========
C#實現圖(Graph)的演算法
http://www.bianceng.cn/Programming/csharp/201311/38092.htm簡介
圖表示點之間的關係,在C#中通過節點物件的集合來表示點(Vertex),用鄰接矩陣(adjacency matrix)來表示點之間的關係。下面來看C#實現。
PS:本片文章是我複習的筆記,程式碼註釋很全。勿吐槽。
表示點的物件
下面實現程式碼:
class Vertex
{
publicstring Data;
publicbool IsVisited;
public Vertex(string Vertexdata)
{
Data = Vertexdata;
}
}
每個節點包含兩個欄位,分別為節點資料以及表示是否被訪問過的一個布林型別。
表示圖的物件
圖中除了需要點的集合和鄰接矩陣之外,還需要一些基本的向圖中新增或刪除元素的方法,以及一個構造方法來對圖進行初始化。
publicclass Graph
{
//圖中所能包含的點上限privateconstint Number = 10;
//頂點陣列private Vertex[] vertiexes;
//鄰接矩陣publicint[,] adjmatrix;
//統計當前圖中有幾個點int numVerts = 0;
//初始化圖public Graph()
{
//初始化鄰接矩陣和頂點陣列
adjmatrix = new Int32[Number, Number];
vertiexes = new Vertex[Number];
//將代表鄰接矩陣的表全初始化為0for (int i = 0; i < Number; i++)
{
for (int j = 0; j < Number; j++)
{
adjmatrix[i, j] = 0;
}
}
}
//向圖中新增節點publicvoid AddVertex(String v)
{
vertiexes[numVerts] = new Vertex(v);
numVerts++;
}
//向圖中新增有向邊publicvoid AddEdge(int vertex1, int vertex2)
{
adjmatrix[vertex1, vertex2] = 1;
//adjmatrix[vertex2, vertex1] = 1;
}
//顯示點publicvoid DisplayVert(int vertexPosition)
{
Console.WriteLine(vertiexes[vertexPosition]+"");
}
}
拓撲排序(TopSort)
拓撲排序是對一個有向的,並且不是環路的圖中所有的頂點線性化。需要如下幾個步驟
1.首先找到沒有後繼的節點。
2.將這個節點加入線性棧中
3.在圖中刪除這個節點
4.重複步驟1,2,3
因此,首先需要找到後繼節點的方法:
//尋找圖中沒有後繼節點的點
//具體表現為鄰接矩陣中某一列全為0//此時返回行號,如果找不到返回-1privateint FindNoSuccessor()
{
bool isEdge;
//迴圈行for (int i = 0; i < numVerts; i++)
{
isEdge = false;
//迴圈列,有一個1就跳出迴圈for (int j = 0; j < numVerts; j++)
{
if (adjmatrix[i, j] == 1)
{
isEdge = true;
break;
}
}
if (!isEdge)
{
return i;
}
}
return -1;
}
========
C#實現二叉查詢樹的演算法
http://www.bianceng.cn/Programming/csharp/201311/38091.htm簡介
樹是一種非線性結構。樹的本質是將一些節點由邊連線起來,形成層級的結構。而二叉樹是一種特殊的樹,使得樹每個子節點必須小於等於2.而二叉查詢樹又是一類特殊的二叉樹。使得每一個節點的左節點或左子樹的所有節點必須小於這個節點,右節點必須大於這個節點。從而方便高效搜尋。
下面來看如何使用C#實現二叉查詢樹。
實現節點
二叉查詢樹是節點的集合。因此首先要構建節點,如程式碼1所示。
//二叉查詢樹的節點定義publicclass Node
{
//節點本身的資料publicint data;
//左孩子public Node left;
//右孩子public Node right;
publicvoid DisplayData()
{
Console.Write(data+"");
}
}
程式碼1.節點的定義
構建二叉樹
構建二叉樹是通過向二叉樹插入元素得以實現的,所有小於根節點的節點插入根節點的左子樹,大於根節點的,插入右子樹。依此類推進行遞迴。直到找到位置進行插入。二叉查詢樹的構建過程其實就是節點的插入過程。C#實現程式碼如程式碼2所示。
publicvoid Insert(int data)
{
Node Parent;
//將所需插入的資料包裝進節點
Node newNode=new Node();
newNode.data=data;
//如果為空樹,則插入根節點if(rootNode==null)
{
rootNode=newNode;
}
//否則找到合適葉子節點位置插入else
{
Node Current = rootNode;
while(true)
{
Parent=Current;
if(newNode.data<Current.data)
{
Current=Current.left;
if(Current==null)
{
Parent.left=newNode;
//插入葉子後跳出迴圈break;
}
}
else
{
Current = Current.right;
if (Current == null)
{
Parent.right = newNode;
//插入葉子後跳出迴圈break;
}
}
}
}
}
程式碼2.實現二叉樹的插入
========