1. 求數據結構題
就行了,准過!
第一章:緒論
1.1:數據結構課程的任務是:討論數據的各種邏輯結構、在計算機中的存儲結構以及各種操作的演算法設計。
1.2:數據:是客觀描述事物的數字、字元以及所有的能輸入到計算機中並能被計算機接收的各種集合的統稱。
數據元素:表示一個事物的一組數據稱作是一個數據元素,是數據的基本單位。
數據項:是數據元素中有獨立含義的、不可分割的最小標識單位。
數據結構概念包含三個方面:數據的邏輯結構、數據的存儲結構的數據的操作。
1.3數據的邏輯結構指數據元素之間的邏輯關系,用一個數據元素的集合定義在此集合上的若干關系來表示,數據結構可以分為三種:線性結構、樹結構和圖。
1.4:數據元素及其關系在計算機中的存儲表示稱為數據的存儲結構,也稱為物理結構。
數據的存儲結構基本形式有兩種:順序存儲結構和鏈式存儲結構。
2.1:演算法:一個演算法是一個有窮規則的集合,其規則確定一個解決某一特定類型問題的操作序列。演算法規則需滿足以下五個特性:
輸入——演算法有零個或多個輸入數據。
輸出——演算法有一個或多個輸出數據,與輸入數據有某種特定關系。
有窮性——演算法必須在執行又窮步之後結束。
確定性——演算法的每個步驟必須含義明確,無二義性。
可行性——演算法的每步操作必須是基本的,它們的原則上都能夠精確地進行,用筆和紙做有窮次就可以完成。
有窮性和可行性是演算法最重要的兩個特徵。
2.2:演算法與數據結構:演算法建立數據結構之上,對數據結構的操作需用演算法來描述。
演算法設計依賴數據的邏輯結構,演算法實現依賴數據結構的存儲結構。
2.3:演算法的設計應滿足五個目標:
正確性:演算法應確切的滿足應用問題的需求,這是演算法設計的基本目標。
健壯性:即使輸入數據不合適,演算法也能做出適當的處理,不會導致不可控結
高時間效率:演算法的執行時間越短,時間效率越高。 果。
高空間效率:演算法執行時佔用的存儲空間越少,空間效率越高。
可讀性:演算法的可讀性有利於人們對演算法的理解。
2.4:度量演算法的時間效率,時間復雜度,(課本39頁)。
2.5:遞歸定義:即用一個概念本身直接或間接地定義它自己。遞歸定義有兩個條件:
至少有一條初始定義是非遞歸的,如1!=1.
由已知函數值逐步遞推計算出未知函數值,如用(n-1)!定義n!。
第二章:線性表
1.1線性表:線性表是由n(n>=0)個類型相同的數據元素a0,a1,a2,…an-1,組成的有限序列,記作: LinearList = (a0,a1,a2,…an-1)
其中,元素ai可以是整數、浮點數、字元、也可以是對象。n是線性表的元素個數,成為線性表長度。若n=0,則LinearList為空表。若n>0,則a0沒有前驅元素,an-1沒有後繼元素,ai(0<i<n-1)有且僅有一個直接前驅元素ai-1和一個直接後繼元素ai+1。
1.2線性表的順序存儲是用一組連續的內存單元依次存放線性表的數據元素,元素在內存的物理存儲次序與它們在線性表中的邏輯次序相同。
線性表的數據元素數據同一種數據類型,設每個元素佔用c位元組,a0的存儲地址為
Loc(a0),則ai的存儲地址Loc(ai)為:Loc(ai) = Loc(a0)+ i*c
數組是順序存儲的隨機存儲結構,它佔用一組連續的存儲單元,通過下標識別元素,元素地址是下標的線性函數。
1.3:順序表的插入和刪除操作要移動數據元素。平均移動次數是 屬數據表長度的一半。(課本第50頁)
1.4:線性表的鏈式存儲是用若乾地址分散的存儲單元存儲數據元素,邏輯上相鄰的數據元素在物理位置上不一定相鄰,必須採用附加信息表示數據元素之間的順序關系。
它有兩個域組成:數據域和地址域。通常成為節點。(課本第55頁及56頁)
1.5單鏈表(課本56頁)
單鏈表的遍歷:Node<E> p = head; while(p!=null)
單鏈表的插入和刪除操作非常簡便,只要改變節點間的鏈接關系,不需移動數據元素。
單鏈表的插入操作:1):空表插入/頭插入 2)中間插入/尾插入
if(head == null) Node<E> q = new Node<E>(x);
{ head = new Node<E>(x); q.next = p.next;
}else{ p.next = q;
Node<E> q=new Node<E>(x); 中間插入或尾插入都不會改變單表
q.next = head; 的頭指針head。
head = q;
}
單鏈表的刪除操作:
頭刪除:head = head.next;
中間/尾刪除:if(p.next!=null)
循環單鏈表:如果單鏈表最後一個節點的next鏈保存單鏈表的頭指針head值,則該單鏈表成為環形結構,稱為循環單鏈表。(課本67)
若rear是單鏈表的尾指針,則執行(rear.next=head;)語句,使單鏈表成為一條循環單鏈表。當head.next==head時,循環單鏈表為空。
1.6:雙鏈表結構:雙鏈表的每個結點有兩個鏈域,分別指向它的前驅和後繼結點,
當head.next==null時,雙鏈表為空。
設p指向雙鏈表中非兩端的某個結點,則成立下列關系:p=p.next.prev=p.prev.next。
雙鏈表的插入和刪除:1)插入 2)刪除
q=new DLinkNode(x); p.prev.next = p.next;
q.prev=p.prev;q.next =p; if(p.next=null){
p.prev.next = q;p.prev=q; (p.next).prev = p.prev;}
循環雙鏈表:當head.next==head且head.prev==head時,循環雙鏈表為空。
第三章:棧和隊列
1.1棧:棧是一種特殊的線性表,其中插入和刪除操作只允許在線性表的一端進行。允許操作的一端稱為棧頂,不允許操作的一端稱為棧底。棧有順序棧和鏈式棧。
棧中插入元素的操作稱為入棧,刪除元素的操作稱為出棧。沒有元素的中稱為空棧。
棧的進出棧順序:後進先出,先進後出。(及75頁的思考題)。
1.2:隊列:隊列是一種特殊的線性表,其中插入和刪除操作分別在線性表的兩端進行。
向隊列中插入元素的過程稱為入隊,刪除元素的過程稱為出對,允許入隊的一端稱為隊尾,允許出隊的一端稱為對頭。沒有元素的隊列稱為空隊列。隊列是先進先出。
第四章:串
1.1:串是一種特殊的線性表,其特殊性在於線性表中的每個元素是一個字元。一個串記為: s=「s0s1s2…sn-1」 其中n>=0,s是串名,一對雙引號括起來的字元序列s0s1s2…sn-1是串值,si(i=0,1,2,…n-1)為特定字元集合中的一個字元。一個串中包含的字元個數稱為串的長度。
長度為0的串稱為空串,記作「」,而由一個或多個空格字元構成的字元串稱為空格串。
子串:由串s中任意連續字元組成的一個子序列sub稱為s的子串,s稱為sub的主串。子串的序號是指該子串的第一個字元在主串中的序號。
串比較:兩個串可比較是否相等,也可比較大小。兩個串(子串)相等的充要條件是兩個串(子串)的長度相同,並且各對應位置上的字元也相同。
兩個串的大小由對應位置的第一個不同字元的大小決定,字元比較次序是從頭開始依次向後。當兩個串長度不等而對應位置的字元都相同時,較長的串定義為較「大」。
第五章:數組和廣義表
1.1:數組是一種數據結構,數據元素具有相同的數據類型。一維數組的邏輯結構是線性表,多維數組是線性表的擴展。
1.2:一維數組:一維數組採用順序存儲結構。一個一維數組佔用一組連續的存儲單元。
設數組第一個元素a0的存儲地址為Loc(a0),每個元素佔用c位元組,則數組其他元素ai的存儲地址Loc(ai)為: Loc(ai)= Loc(a0)+i*c
數組通過下標識別元素,元素地址是下標的線性函數。一個下標能夠唯一確定一個元素,所劃給的時間是O(1)。因此數組是隨機存取結構,這是數組最大的優點。
1.3:多維數組的遍歷:有兩種次序:行主序和列主序。
行主序:以行為主序,按行遞增訪問數組元素,訪問完第i行的所有元素之後再訪問第i+1行的元素,同一行上按列遞增訪問數組元素。
a00,a01,…a0(n-1), a10,a11,…a1(n-1),…a(m-1)0,a(m-1)1,…,a(m-1)(n-1)
2)列主序:以列為主序,按列遞增訪問數組元素,訪問完第j列的所有元素之後再訪問第j+1列的元素,同一列上按列遞增訪問數組元素。
多維數組的存儲結構:多維數組也是由多個一維數組組合而成,組合方式有一下兩種。
靜態多維數組的順序存儲結構:可按行主序和列主序進行順序存儲。
按行主序存儲時,元素aij的地址為:Loc(aij)= Loc(a00)+(i*n+j)*c
按列主序存儲時,Loc(aij)= Loc(a00)+(j*m+i)*c
動態多維數組的存儲結構。
二維數組元素地址就是兩個下標的線性函數。無論採用哪種存儲結構,多維數組都是基於一維數組的,因此也只能進行賦值、取值兩種存取操作,不能進行插入,刪除操作。
第六章:
樹是數據元素(結點)之間具有層次關系的非線性結構。在樹結構中,除根以外的結點只有一個直接前驅結點,可以有零至多個直接後繼結點。根沒有前驅結點。
樹是由n(n>=0)個結點組成的有限集合(樹中元素通常稱為結點)。N=0的樹稱為空樹;n>0大的樹T;
@有一個特殊的結點稱為根結點,它只有後繼結點,沒有前驅結點。
@除根結點之外的其他結點分為m(m>=0)個互不相交的集合T0,T1,T3……..,Tm-1,其中每個集合Ti(0<=i<m)本身又是一棵樹,稱為根的子樹。
樹是遞歸定義的。結點是樹大的基本單位,若干個結點組成一棵子樹,若干棵互不相交的子樹組成一棵樹。樹的每個結點都是該樹中某一棵子樹的根。因此,樹是由結點組成的、結點之間具有層次關系大的非線性結構。
結點的前驅結點稱為其父母結點,反之,結點大的後繼結點稱為其孩子結點。一棵樹中,只有根結點沒有父母結點,其他結點有且僅有一個父母結點。
擁有同一個父母結點的多個結點之間稱為兄弟結點。結點的祖先是指從根結點到其父母結點所經過大的所有結點。結點的後代是指該結點的所有孩子結點,以及孩子的孩子等。
結點的度是結點所擁有子樹的棵數。度為0的結點稱為葉子結點,又叫終端結點;樹中除葉子結點之外的其他結點稱為分支結點,又叫非葉子結點或非終端結點。樹的度是指樹中各結點度的最大值。
結點的層次屬性反應結點處於樹中的層次位置。約定根結點的層次為1,其他結點的層次是其父母結點的層次加1。顯然,兄弟結點的層次相同。
樹的高度或深度是樹中結點的最大層次樹。
設樹中x結點是y結點的父母結點,有序對(x,y)稱為連接這兩個結點的分支,也稱為邊。
設(X0,X1,….,Xk-1)是由樹中結點組成的一個序列,且(Xi,Xi+1)(0<=i<k-1)都是樹中的邊,則該序列稱為從X0到Xk-1的一條路徑。路徑長度為路徑上的邊數。
在樹的定義中,結點的子樹T0,T1…..,Tm-1之間沒有次序,可以交換位置,稱為無序樹,簡稱樹。如果結點的子樹T0,T1……,Tm-1從左到右是有次序的,不能交換位置,則 稱該樹為有序樹。
森林是m(m>=0)棵互不相乾的樹的集合。給森林加上一個根結點就變成一棵樹,將樹的根節點刪除就變成森林。
二叉樹的性質1:若根結點的層次為1,則二叉樹第i層最多有2 的i-1次方(i>=1)個結點。
二叉樹的性質2:在高度為k的二叉樹中,最多有2的k次方減一個結點。
二叉樹的性質3:設一棵二叉樹的葉子結點數為n0,2度結點數為n2,則n0=n2+1。
一棵高度為k的滿二叉樹是具有2的k次方減一個結點的二叉樹。滿二叉樹中每一層的結點數目都達到最大值。對滿二叉樹的結點進行連續編號,約定根節點的序號為0,從根節點開始,自上而下,每層自左至右編號。
一棵具有n個結點高度為k的二叉樹,如果他的每個節點都與高度為k的滿二叉樹中序號為0~n-1
的結點一一對應,則這棵二叉樹為為完全二叉樹。
滿二叉樹是完全二叉樹,而完全二叉樹不一定是滿二叉樹。完全二叉樹的第1~k-1層是滿二叉樹第k層不滿,並且該層所有結點必須集中在該層左邊的若干位置上。
二叉樹的性質4:一棵具有n個結點的完全二叉樹,其高度k=log2n的絕對值+1
二叉樹的性質5:一棵具有n個結點的完全二叉樹,對序號為i的結點,有
@若i=0,則i為根節點,無父母結點;若i>0,則i的父母結點的序號為[(i-1)/2]。
@若2i+1<n,則i的左孩子結點序號為2i+1;否則i無左孩子。
@若2i+2<n,則i的右孩子結點的序號為2i+2,否則i無右孩子。
二叉樹的遍歷
二叉樹的遍歷是按照一定規則和次序訪問二叉樹中的所有結點,並且每個結點僅被訪問一次。
二叉樹的三種次序遍歷
1:先根次序;訪問根節點,遍歷左子樹,遍歷右子樹。
2:中根次序;遍歷左子樹,訪問右子樹,遍歷右子樹。
3:後根次序;遍歷左子樹,遍歷右子樹,訪問根節點。
先根次序遍歷時,最先訪問根節點;後根次序遍歷時,最後訪問根節點;中根次序遍歷時,左子樹上的結點在根節點之前訪問,右子樹上的結點在根節點之後訪問。
二叉樹的插入和刪除操作P147
二叉樹的層次遍歷P149
習題P167 6—10,6—19
第七章
圖是由定點集合及頂點間的關系集合組成的一種數據關邊系。頂點之間的關系成為邊。一個圖G記為G=(V,E),V是頂點A的有限集合,E是邊的有限集合。即 V=
E=或E=其中Path(A,B)表示從頂點A到B的一條單向通路,即Path(A,B)是有方向的。
無向圖中的邊事沒有方向,每條邊用兩個頂點的無序對表示。
有向圖中的邊是有方向,每條邊用兩個頂點的有序對表示。
完全圖指圖的邊數達到最大值。n個頂點的完全圖記為Kn。無向完全圖Kn的邊數為n*(n-1)/2,有向完全圖Kn的邊數為n*(n-1)。
子圖:設圖G==(V,E),G』=(V』,E』),若V』包含於V且E』包含於E,則稱圖G』是G的子圖。若G』是G的真子圖。
連通圖:在無向圖G中,若從頂點VI到Vj有路徑,則稱Vi和Vj是聯通的。若圖G中任意一對頂點Vi和Vj(Vi不等於Vj)都是聯通的,則稱G為連通圖。非連通圖的極大聯通子圖稱為該圖的聯通分量。
強連通圖:在有向圖中,若在每一對頂點Vi和Vj(Vi不等於Vj)之間都存在一條從Vi到Vj的路徑,也存在一條從Vi到Vj的路徑,也存在一條從Vi到Vj的路徑,則稱該圖的強連通圖。非強連通圖的極大強連通子圖稱為該圖的強連通圖分量。
圖的遍歷
遍歷圖是指從圖G中任意一個頂點V出發,沿著圖中的邊前行,到達並訪問圖中的所有頂點,且每個頂點僅被訪問一次。遍歷圖要考慮一下三個問題:
@指定遍歷的第一個訪問頂點
@由於一個頂點可能與多個頂點相鄰,因此要在多個鄰接頂點之間約定一種訪問次序。
@由於圖中可能存在迴路,在訪問某個頂點之後,可能沿著某條路徑又回到該頂點。
深度優先搜索
圖的深度優先搜索策略是,訪問某個頂點v,接著尋找v的另一個未被訪問的鄰接頂點w訪問,如此反復執行,走過一條較長路徑到達最遠頂點;若頂點v沒有未被訪問的其他鄰接頂點,則回到前一個被訪問頂點,再尋找其他訪問路徑。
圖的深度優先搜索遍歷演算法P188
聯通的無迴路的無向圖,簡稱樹。樹中的懸掛點又成為樹葉,其他頂點稱為分支點。各連通分量均為樹的圖稱為森林,樹是森林。
由於樹中無迴路,因此樹中必定無自身環也無重邊(否則他有迴路)若去掉樹中的任意一條邊,則變成森林,成為非聯通圖;若給樹加上一條邊,形成圖中的一條迴路,則不是樹。P191
生成樹和生成森林:
一個連通無向圖的生成樹是該圖的一個極小聯通生成子圖,它包含原圖中所有頂點(n個)以及足以構成一棵樹的n-1條邊。
一個非聯通的無向圖,其各連通圖分量的生成圖組成該圖的生成森林。
圖的生成圖或生成森林不是唯一的,從不同頂點開始、採用不同遍歷可以得到不同的生成樹或森林。
在生成樹中,任何樹中,任何兩個頂點之間只有唯一的一條路徑。
第八章
折半查找演算法描述 P206,P207
二叉排序樹及其查找:
二叉排序樹或者是一棵空樹;或者是具有下列性質的二叉樹:
@每個結點都有一個作為查找依據的關鍵字,所有結點的關鍵字互不相同。
@若一個結點的左子樹不空,則左子樹上所有結點的關鍵字均小於這個節點的關鍵字;
@每個結點的左右子樹也分別為二叉排序樹。
在一棵二叉排序樹中,查找值為value的結點,演算法描述如下:
@從根結點開始,設p指向根結點
@將value與p結點的關鍵字進行比較,若兩者相等,則查找成功;若value值較小,則在p的左子樹中繼續查找;若value值較大,則在p的右子樹中繼續查找。
@重復執行上一步,直到查找成功或p為空,若p為空,則查找不成功。
習題 8-6
第九章
直接插入排序演算法描述:p228
冒泡排序演算法的描述:p232
快速排序演算法描述p233
直接選擇排序演算法描述p236
直接選擇排序演算法實現如下:
Public static void selectSort(int[]table){
for(int i=0;i<table.length-1;i++){
int min=I;
for(int j=i+1;j<table.length;j++){
if(table[j]<table[min])
min=j;
if(min!=i){
int temp=table[i];
table[i]==table[min];
table[min]=temp;
}
}
}
}
堆排序是完全二叉樹的應用,是充分利用完全二叉樹特性的一種選擇排序。
堆定義:設n個元素的數據序列,當且僅當滿足下列關系
k1<=k2i+1且ki<=k2i+2 i=0,1,2,3,….,[n/2-1]
或ki>==k2i+1且ki>=2i+2i=0,1,2,3,…..[n/2-1]時,序列稱為最小堆或最大堆。將最小(大)堆看成是一顆完全二叉樹的層次遍歷序列,則任意一個結點的關鍵字都小於等於(大於等於)它的孩子節點的關鍵字值,由此可知,根結點值最小(大)。根據二叉樹的性質5,完全二叉樹中的第i(0<=i<n)個結點,如果有孩子,則左孩子為第2i+1個結點,右孩子為第2i+2個結點。
希望對你會有所幫助。
2. 1、設計一個用帶頭結點的單鏈表表示的直接插入排序演算法,各結點結構如圖:
首先,你沒有想清楚你就開始寫程序了,其次,你的寫程序的習慣不好。下班回來看了下你的程序,為了節省時間,我在你的程序稍加改動,並只改了從高到低的情況,現將問題描述如下,在程序中也會注釋出來:1、可能是因為我的系統是日文的,你程序上的解釋就出現了問題,請注意檢查。2、cur指代不明。3、結束條件判斷錯誤,有兩處。下面在程序中詳細說明,我是在你的程序上改的:#include<stdio.h>#include<stdlib.h>#define debug 1struct arry;void main() do if(temp->next->next==NULL)last=cur;/*這是說當新結點是最小的時候就直接插到尾結點後面,同時尾結點更新*/ } goto part2; /*下面一種情況就沒有改,參照上面的錯誤自己改吧,我也沒多少時間,呵呵*/ break; case 'd': case 'D': for (temp=head;temp->next!=NULL;temp=temp->next) } break; } part2: for (temp=head;t
3. 能不能幫我做一個常用排序演算法的設計呢 跪求啊 幫幫忙吧 我對這個一竅不通啊
常用的排序有:
1.直接插入排序
2.希爾排序(最小增量排序)
3.簡單選擇排序
4.堆排序
5.冒泡排序
6.快速排序
7.歸並排序
8.基數排序
4. 課程設計:採用單鏈表存儲待排序的數據,實現直接插入排序演算法.求演算法
template<class T>
void LinkList<T>::InsertSort()
{
Node<T> * s=First->Next;
Node<T> * t=s->Next;
while(t!=NULL)
{
Node<T> * r=First;
while(s!=NULL)
{
if(t->Data<s->Data)
{
Node<T> * p=new Node<T>;
p->Data=t->Data;
p->Next=s;
r->Next=p;
s=NULL;
}
else if(t->Data==s->Data)
{
Node<T> * q=new Node<T>;
q->Data=t->Data;
q->Next=NULL;
r->Next=q;
s=NULL;
}
else
{
r=s;
s=s->Next;
}
}
Node<T> * h=t;
t=t->Next;
delete h;
s=First->Next;
}
}
5. 排序演算法的實現與比較的課程設計
;
#include<stdio.h>
#define NUM 7 //宏定義
int i; //變數類型定義
typedef struct Node{
int data ; //數據域
struct Node *next; //指針域
}Node,*LNode; //用結構體構造結點及相應的指針
typedef struct Tree{
int data ;
struct Tree *left ;
struct Tree *right ;
}Tree,*LTree ; //用結構體構造樹及相應的指針
CreateList( LNode Head ) //創建單鏈表
{
for(int i=1 ; i <=NUM ; i++) //創建循環,依次輸入NUM個數據
{
LNode temp ; //中間結點
temp = (LNode) malloc( sizeof( Node ) ); //動態存儲分配
temp-> next = NULL; //中間結點初始化
scanf("%2d",&temp-> data); //輸入賦值到結點temp數據域
temp-> next = Head-> next ;
Head-> next = temp ; //將temp結點插入鏈表
}
return 1 ;//返回1
}
InsertSqTree( LTree &root , LNode temp ) //二叉樹排序原則的設定
{
if(!root) //root為NULL時執行
{
root = (LTree)malloc(sizeof(Tree)); //動態存儲分配
root-> left =NULL;
root-> right=NULL; //初始化
root-> data = temp-> data ; //賦值插入
return 1 ; //函數正常執行,返回1
}
else
{
if(root-> data>= temp-> data)
return InsertSqTree( root-> left , temp ) ; //比較插入左子樹
else if(root-> data <temp-> data)
return InsertSqTree( root-> right , temp ); //比較插入右子樹
}
return 1 ; //如果滿足,就不做處理,返回1
}
void BianLiTree(LTree root) //採用中序遍歷,實現將所有數字按從左向右遞增的順序排序
{
if(root) //root不為空執行
{BianLiTree(root-> left); //左遞歸處理至葉子結點,當root-> left為NULL時不執行
printf("%4d ",root-> data); //輸出
BianLiTree(root-> right); //處理右結點
}
}
int main()
{
LNode Head = NULL;
LTree root = NULL ; //初始化
Head = (LNode) malloc(sizeof(Node)); //動態存儲分配
Head-> next = NULL ; //初始化
printf("please input numbers:\n");//輸入提示語句
if(!CreateList( Head )) //建單鏈表成功返回1不執行下一語句
return 0; //結束函數,返回0
LNode temp = Head-> next ; //將頭指針的指針域賦值予中間結點
while( temp ) //temp為NULL時停止執行
{
if(!InsertSqTree( root ,temp )) //排序正常執行,返回1不執行下一語句
return 0 ; //結束函數,返回0
Head-> next = temp-> next ; //將中間指針的指針域賦值予頭結點指針域
free(temp); //釋放空間
temp = Head-> next ; //將頭指針的指針域賦值予中間結點,以上三句實現了temp指針後移
}
printf("the result is:\n");//輸出提示語句
BianLiTree(root); //採用中序遍歷,輸出並觀察樹結點
return 1; //函數正常結,返回1
}
6. python幾種經典排序方法的實現
class SortMethod:
'''
插入排序的基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據,演算法適用於少量數據的排序,時間復雜度為O(n^2)。是穩定的排序方法。
插入演算法把要排序的數組分成兩部分:
第一部分包含了這個數組的所有元素,但將最後一個元素除外(讓數組多一個空間才有插入的位置)
第二部分就只包含這一個元素(即待插入元素)。
在第一部分排序完成後,再將這個最後元素插入到已排好序的第一部分中。
'''
def insert_sort(lists):
# 插入排序
count = len(lists)
for i in range(1, count):
key = lists[i]
j = i - 1
while j >= 0:
if lists[j] > key:
lists[j + 1] = lists[j]
lists[j] = key
j -= 1
return lists
'''
希爾排序 (Shell Sort) 是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。該方法因 DL.Shell 於 1959 年提出而得名。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至 1 時,整個文件恰被分成一組,演算法便終止。
'''
def shell_sort(lists):
# 希爾排序
count = len(lists)
step = 2
group = count / step
while group > 0:
for i in range(0, group):
j = i + group
while j < count:
k = j - group
key = lists[j]
while k >= 0:
if lists[k] > key:
lists[k + group] = lists[k]
lists[k] = key
k -= group
j += group
group /= step
return lists
'''
冒泡排序重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。
'''
def bubble_sort(lists):
# 冒泡排序
count = len(lists)
for i in range(0, count):
for j in range(i + 1, count):
if lists[i] > lists[j]:
temp = lists[j]
lists[j] = lists[i]
lists[i] = temp
return lists
'''
快速排序
通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列
'''
def quick_sort(lists, left, right):
# 快速排序
if left >= right:
return lists
key = lists[left]
low = left
high = right
while left < right:
while left < right and lists[right] >= key:
right -= 1
lists[left] = lists[right]
while left < right and lists[left] <= key:
left += 1
lists[right] = lists[left]
lists[right] = key
quick_sort(lists, low, left - 1)
quick_sort(lists, left + 1, high)
return lists
'''
直接選擇排序
第 1 趟,在待排序記錄 r[1] ~ r[n] 中選出最小的記錄,將它與 r[1] 交換;
第 2 趟,在待排序記錄 r[2] ~ r[n] 中選出最小的記錄,將它與 r[2] 交換;
以此類推,第 i 趟在待排序記錄 r[i] ~ r[n] 中選出最小的記錄,將它與 r[i] 交換,使有序序列不斷增長直到全部排序完畢。
'''
def select_sort(lists):
# 選擇排序
count = len(lists)
for i in range(0, count):
min = i
for j in range(i + 1, count):
if lists[min] > lists[j]:
min = j
temp = lists[min]
lists[min] = lists[i]
lists[i] = temp
return lists
'''
堆排序 (Heapsort) 是指利用堆積樹(堆)這種數據結構所設計的一種排序演算法,它是選擇排序的一種。
可以利用數組的特點快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即 A[PARENT[i]] >= A[i]。
在數組的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。
'''
# 調整堆
def adjust_heap(lists, i, size):
lchild = 2 * i + 1
rchild = 2 * i + 2
max = i
if i < size / 2:
if lchild < size and lists[lchild] > lists[max]:
max = lchild
if rchild < size and lists[rchild] > lists[max]:
max = rchild
if max != i:
lists[max], lists[i] = lists[i], lists[max]
adjust_heap(lists, max, size)
# 創建堆
def build_heap(lists, size):
for i in range(0, (size/2))[::-1]:
adjust_heap(lists, i, size)
# 堆排序
def heap_sort(lists):
size = len(lists)
build_heap(lists, size)
for i in range(0, size)[::-1]:
lists[0], lists[i] = lists[i], lists[0]
adjust_heap(lists, 0, i)
'''
歸並排序是建立在歸並操作上的一種有效的排序演算法,該演算法是採用分治法 (Divide and Conquer) 的一個非常典型的應用。將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為二路歸並。
歸並過程為:
比較 a[i] 和 a[j] 的大小,若 a[i]≤a[j],則將第一個有序表中的元素 a[i] 復制到 r[k] 中,並令 i 和 k 分別加上 1;
否則將第二個有序表中的元素 a[j] 復制到 r[k] 中,並令 j 和 k 分別加上 1,如此循環下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素復制到 r 中從下標 k 到下標 t 的單元。歸並排序的演算法我們通常用遞歸實現,先把待排序區間 [s,t] 以中點二分,接著把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸並操作合並成有序的區間 [s,t]。
'''
def merge(left, right):
i, j = 0, 0
result = []
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
def merge_sort(lists):
# 歸並排序
if len(lists) <= 1:
return lists
num = len(lists) / 2
left = merge_sort(lists[:num])
right = merge_sort(lists[num:])
return merge(left, right)
'''
基數排序 (radix sort) 屬於「分配式排序」 (distribution sort),又稱「桶子法」 (bucket sort) 或 bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些「桶」中,藉以達到排序的作用,基數排序法是屬於穩定性的排序。
其時間復雜度為 O (nlog(r)m),其中 r 為所採取的基數,而 m 為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。
'''
import math
def radix_sort(lists, radix=10):
k = int(math.ceil(math.log(max(lists), radix)))
bucket = [[] for i in range(radix)]
for i in range(1, k+1):
for j in lists:
bucket[j/(radix**(i-1)) % (radix**i)].append(j)
del lists[:]
for z in bucket:
lists += z
del z[:]
return lists
---------------------
作者:CRazyDOgen
來源:CSDN
原文:https://blog.csdn.net/jipang6225/article/details/79975312
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
7. 基於C語言的幾種排序演算法的分析
相關知識介紹(所有定義只為幫助讀者理解相關概念,並非嚴格定義):
1、穩定排序和非穩定排序
簡單地說就是所有相等的數經過某種排序方法後,仍能保持它們在排序之前的相對次序,我們就
說這種排序方法是穩定的。反之,就是非穩定的。
比如:一組數排序前是a1,a2,a3,a4,a5,其中a2=a4,經過某種排序後為a1,a2,a4,a3,a5,
則我們說這種排序是穩定的,因為a2排序前在a4的前面,排序後它還是在a4的前面。假如變成a1,a4,
a2,a3,a5就不是穩定的了。
2、內排序和外排序
在排序過程中,所有需要排序的數都在內存,並在內存中調整它們的存儲順序,稱為內排序;
在排序過程中,只有部分數被調入內存,並藉助內存調整數在外存中的存放順序排序方法稱為外排序。
3、演算法的時間復雜度和空間復雜度
所謂演算法的時間復雜度,是指執行演算法所需要的計算工作量。
一個演算法的空間復雜度,一般是指執行這個演算法所需要的內存空間。
================================================================================
*/
/*
================================================
功能:選擇排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:
在要排序的一組數中,選出最小的一個數與第一個位置的數交換;
然後在剩下的數當中再找最小的與第二個位置的數交換,如此循環
到倒數第二個數和最後一個數比較為止。
選擇排序是不穩定的。演算法復雜度O(n2)--[n的平方]
=====================================================
*/
void select_sort(int *x, int n)
{
int i, j, min, t;
for (i=0; i<n-1; i++) /*要選擇的次數:0~n-2共n-1次*/
{
min = i; /*假設當前下標為i的數最小,比較後再調整*/
for (j=i+1; j<n; j++)/*循環找出最小的數的下標是哪個*/
{
if (*(x+j) < *(x+min))
{
min = j; /*如果後面的數比前面的小,則記下它的下標*/
}
}
if (min != i) /*如果min在循環中改變了,就需要交換數據*/
{
t = *(x+i);
*(x+i) = *(x+min);
*(x+min) = t;
}
}
}
/*
================================================
功能:直接插入排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:
在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排
好順序的,現在要把第n個數插到前面的有序數中,使得這n個數
也是排好順序的。如此反復循環,直到全部排好順序。
直接插入排序是穩定的。演算法時間復雜度O(n2)--[n的平方]
=====================================================
*/
void insert_sort(int *x, int n)
{
int i, j, t;
for (i=1; i<n; i++) /*要選擇的次數:1~n-1共n-1次*/
{
/*
暫存下標為i的數。注意:下標從1開始,原因就是開始時
第一個數即下標為0的數,前面沒有任何數,單單一個,認為
它是排好順序的。
*/
t=*(x+i);
for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,這里就是下標為i的數,在它前面有序列中找插入位置。*/
{
*(x+j+1) = *(x+j); /*如果滿足條件就往後挪。最壞的情況就是t比下標為0的數都小,它要放在最前面,j==-1,退出循環*/
}
*(x+j+1) = t; /*找到下標為i的數的放置位置*/
}
}
/*
================================================
功能:冒泡排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:
在要排序的一組數中,對當前還未排好序的范圍內的全部數,自上
而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較
小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要
求相反時,就將它們互換。
下面是一種改進的冒泡演算法,它記錄了每一遍掃描後最後下沉數的
位置k,這樣可以減少外層循環掃描的次數。
冒泡排序是穩定的。演算法時間復雜度O(n2)--[n的平方]
=====================================================
*/
void bubble_sort(int *x, int n)
{
int j, k, h, t;
for (h=n-1; h>0; h=k) /*循環到沒有比較范圍*/
{
for (j=0, k=0; j<h; j++) /*每次預置k=0,循環掃描後更新k*/
{
if (*(x+j) > *(x+j+1)) /*大的放在後面,小的放到前面*/
{
t = *(x+j);
*(x+j) = *(x+j+1);
*(x+j+1) = t; /*完成交換*/
k = j; /*保存最後下沉的位置。這樣k後面的都是排序排好了的。*/
}
}
}
}
/*
================================================
功能:希爾排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:
在直接插入排序演算法中,每次插入一個數,使有序序列只增加1個節點,
並且對插入下一個數沒有提供任何幫助。如果比較相隔較遠距離(稱為
增量)的數,使得數移動時能跨過多個元素,則進行一次比較就可能消除
多個元素交換。D.L.shell於1959年在以他名字命名的排序演算法中實現
了這一思想。演算法先將要排序的一組數按某個增量d分成若干組,每組中
記錄的下標相差d.對每組中全部元素進行排序,然後再用一個較小的增量
對它進行,在每組中再進行排序。當增量減到1時,整個要排序的數被分成
一組,排序完成。
下面的函數是一個希爾排序演算法的一個實現,初次取序列的一半為增量,
以後每次減半,直到增量為1。
希爾排序是不穩定的。
=====================================================
*/
void shell_sort(int *x, int n)
{
int h, j, k, t;
for (h=n/2; h>0; h=h/2) /*控制增量*/
{
for (j=h; j<n; j++) /*這個實際上就是上面的直接插入排序*/
{
t = *(x+j);
for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
{
*(x+k+h) = *(x+k);
}
*(x+k+h) = t;
}
}
}
/*
================================================
功能:快速排序
輸入:數組名稱(也就是數組首地址)、數組中起止元素的下標
================================================
*/
/*
====================================================
演算法思想簡單描述:
快速排序是對冒泡排序的一種本質改進。它的基本思想是通過一趟
掃描後,使得排序序列的長度能大幅度地減少。在冒泡排序中,一次
掃描只能確保最大數值的數移到正確位置,而待排序序列的長度可能只
減少1。快速排序通過一趟掃描,就能確保某個數(以它為基準點吧)
的左邊各數都比它小,右邊各數都比它大。然後又用同樣的方法處理
它左右兩邊的數,直到基準點的左右只有一個元素為止。它是由
C.A.R.Hoare於1962年提出的。
顯然快速排序可以用遞歸實現,當然也可以用棧化解遞歸實現。下面的
函數是用遞歸實現的,有興趣的朋友可以改成非遞歸的。
快速排序是不穩定的。最理想情況演算法時間復雜度O(nlog2n),最壞O(n2)
=====================================================
*/
void quick_sort(int *x, int low, int high)
{
int i, j, t;
if (low < high) /*要排序的元素起止下標,保證小的放在左邊,大的放在右邊。這里以下標為low的元素為基準點*/
{
i = low;
j = high;
t = *(x+low); /*暫存基準點的數*/
while (i<j) /*循環掃描*/
{
while (i<j && *(x+j)>t) /*在右邊的只要比基準點大仍放在右邊*/
{
j--; /*前移一個位置*/
}
if (i<j)
{
*(x+i) = *(x+j); /*上面的循環退出:即出現比基準點小的數,替換基準點的數*/
i++; /*後移一個位置,並以此為基準點*/
}
while (i<j && *(x+i)<=t) /*在左邊的只要小於等於基準點仍放在左邊*/
{
i++; /*後移一個位置*/
}
if (i<j)
{
*(x+j) = *(x+i); /*上面的循環退出:即出現比基準點大的數,放到右邊*/
j--; /*前移一個位置*/
}
}
*(x+i) = t; /*一遍掃描完後,放到適當位置*/
quick_sort(x,low,i-1); /*對基準點左邊的數再執行快速排序*/
quick_sort(x,i+1,high); /*對基準點右邊的數再執行快速排序*/
}
}
/*
================================================
功能:堆排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
================================================
*/
/*
====================================================
演算法思想簡單描述:
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義如下:具有n個元素的序列(h1,h2,...,hn),當且僅當
滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
時稱之為堆。在這里只討論滿足前者條件的堆。
由堆的定義可以看出,堆頂元素(即第一個元素)必為最大項。完全二叉樹可以
很直觀地表示堆的結構。堆頂為根,其它為左子樹、右子樹。
初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲順序,
使之成為一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點
交換。然後對前面(n-1)個數重新調整使之成為堆。依此類推,直到只有兩個節點
的堆,並對它們作交換,最後得到有n個節點的有序序列。
從演算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素
交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反復調用滲透函數
實現排序的函數。
堆排序是不穩定的。演算法時間復雜度O(nlog2n)。
*/
/*
功能:滲透建堆
輸入:數組名稱(也就是數組首地址)、參與建堆元素的個數、從第幾個元素開始
*/
void sift(int *x, int n, int s)
{
int t, k, j;
t = *(x+s); /*暫存開始元素*/
k = s; /*開始元素下標*/
j = 2*k + 1; /*右子樹元素下標*/
while (j<n)
{
if (j<n-1 && *(x+j) < *(x+j+1))/*判斷是否滿足堆的條件:滿足就繼續下一輪比較,否則調整。*/
{
j++;
}
if (t<*(x+j)) /*調整*/
{
*(x+k) = *(x+j);
k = j; /*調整後,開始元素也隨之調整*/
j = 2*k + 1;
}
else /*沒有需要調整了,已經是個堆了,退出循環。*/
{
break;
}
}
*(x+k) = t; /*開始元素放到它正確位置*/
}
/*
功能:堆排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
*/
void heap_sort(int *x, int n)
{
int i, k, t;
int *p;
for (i=n/2-1; i>=0; i--)
{
sift(x,n,i); /*初始建堆*/
}
for (k=n-1; k>=1; k--)
{
t = *(x+0); /*堆頂放到最後*/
*(x+0) = *(x+k);
*(x+k) = t;
sift(x,k,0); /*剩下的數再建堆*/
}
}
void main()
{
#define MAX 4
int *p, i, a[MAX];
/*錄入測試數據*/
p = a;
printf("Input %d number for sorting :\n",MAX);
for (i=0; i<MAX; i++)
{
scanf("%d",p++);
}
printf("\n");
/*測試選擇排序*/
p = a;
select_sort(p,MAX);
/**/
/*測試直接插入排序*/
/*
p = a;
insert_sort(p,MAX);
*/
/*測試冒泡排序*/
/*
p = a;
insert_sort(p,MAX);
*/
/*測試快速排序*/
/*
p = a;
quick_sort(p,0,MAX-1);
*/
/*測試堆排序*/
/*
p = a;
heap_sort(p,MAX);
*/
for (p=a, i=0; i<MAX; i++)
{
printf("%d ",*p++);
}
printf("\n");
system("pause");
}
8. 設有長度為n的數組a,請設計直接插入排序演算法
void lnsertSort(SeqList R)
{ //對順序表R中的記錄R[1..n]按遞增序進行插入排序
int i,j;
for(i=2;i<=n;i++) //依次插入R[2],…,R[n]
if(R[i].key<R[i-1].key){//若R[i].key大於等於有序區中所有的keys,則R[i]
//應在原有位置上
R[0]=R[i];j=i-1; //R[0]是哨兵,且是R[i]的副本
do{ //從右向左在有序區R[1..i-1]中查找R[i]的插入位置
R[j+1]=R[j]; //將關鍵字大於R[i].key的記錄後移
j-- ;
}while(R[0].key<R[j].key); //當R[i].key≥R[j].key時終止
R[j+1]=R[0]; //R[i]插入到正確的位置上
}//endif
}//InsertSort
9. 排序演算法如何實現 C++
一、簡單排序演算法
由於程序比較簡單,所以沒有加什麼注釋。所有的程序都給出了完整的運行代碼,並在我的VC環境
下運行通過。因為沒有涉及MFC和WINDOWS的內容,所以在BORLAND C++的平台上應該也不會有什麼
問題的。在代碼的後面給出了運行過程示意,希望對理解有幫助。
1.冒泡法:
這是最原始,也是眾所周知的最慢的演算法了。他的名字的由來因為它的工作看來象是冒泡:
#include <iostream.h>
void BubbleSort(int* pData,int Count)
{
int iTemp;
for(int i=1;i<Count;i++)
{
for(int j=Count-1;j>=i;j--)
{
if(pData[j]<pData[j-1])
{
iTemp = pData[j-1];
pData[j-1] = pData[j];
pData[j] = iTemp;
}
}
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
BubbleSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交換3次)
第二輪:7,10,9,8->7,10,8,9->7,8,10,9(交換2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:6次
其他:
第一輪:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交換2次)
第二輪:7,8,10,9->7,8,10,9->7,8,10,9(交換0次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
上面我們給出了程序段,現在我們分析它:這里,影響我們演算法性能的主要部分是循環和交換, 顯然,次數越多,性能就越差。從上面的程序我們可以看出循環的次數是固定的,為1+2+...+n-1。 寫成公式就是1/2*(n-1)*n。 現在注意,我們給出O方法的定義:
若存在一常量K和起點n0,使當n>=n0時,有f(n)<=K*g(n),則f(n) = O(g(n))。(呵呵,不要說沒 學好數學呀,對於編程數學是非常重要的!!!)
現在我們來看1/2*(n-1)*n,當K=1/2,n0=1,g(n)=n*n時,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n) =O(g(n))=O(n*n)。所以我們程序循環的復雜度為O(n*n)。 再看交換。從程序後面所跟的表可以看到,兩種情況的循環相同,交換不同。其實交換本身同數據源的 有序程度有極大的關系,當數據處於倒序的情況時,交換次數同循環一樣(每次循環判斷都會交換), 復雜度為O(n*n)。當數據為正序,將不會有交換。復雜度為O(0)。亂序時處於中間狀態。正是由於這樣的 原因,我們通常都是通過循環次數來對比演算法。
2.交換法:
交換法的程序最清晰簡單,每次用當前的元素一一的同其後的元素比較並交換。
#include <iostream.h>
void ExchangeSort(int* pData,int Count)
{
int iTemp;
for(int i=0;i<Count-1;i++)
{
for(int j=i+1;j<Count;j++)
{
if(pData[j]<pData[i])
{
iTemp = pData[i];
pData[i] = pData[j];
pData[j] = iTemp;
}
}
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
ExchangeSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交換3次)
第二輪:7,10,9,8->7,9,10,8->7,8,10,9(交換2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:6次
其他:
第一輪:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交換1次)
第二輪:7,10,8,9->7,8,10,9->7,8,10,9(交換1次)
第一輪:7,8,10,9->7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
從運行的表格來看,交換幾乎和冒泡一樣糟。事實確實如此。循環次數和冒泡一樣 也是1/2*(n-1)*n,所以演算法的復雜度仍然是O(n*n)。由於我們無法給出所有的情況,所以 只能直接告訴大家他們在交換上面也是一樣的糟糕(在某些情況下稍好,在某些情況下稍差)。
3.選擇法:
現在我們終於可以看到一點希望:選擇法,這種方法提高了一點性能(某些情況下) 這種方法類似我們人為的排序習慣:從數據中選擇最小的同第一個值交換,在從省下的部分中 選擇最小的與第二個交換,這樣往復下去。
#include <iostream.h>
void SelectSort(int* pData,int Count)
{
int iTemp;
int iPos;
for(int i=0;i<Count-1;i++)
{
iTemp = pData[i];
iPos = i;
for(int j=i+1;j<Count;j++)
{
if(pData[j]<iTemp)
{
iTemp = pData[j];
iPos = j;
}
}
pData[iPos] = pData[i];
pData[i] = iTemp;
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
SelectSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交換1次)
第二輪:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交換1次)
第一輪:7,8,9,10->(iTemp=9)7,8,9,10(交換0次)
循環次數:6次
交換次數:2次
其他:
第一輪:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交換1次)
第二輪:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交換1次)
第一輪:7,8,10,9->(iTemp=9)7,8,9,10(交換1次)
循環次數:6次
交換次數:3次
遺憾的是演算法需要的循環次數依然是1/2*(n-1)*n。所以演算法復雜度為O(n*n)。 我們來看他的交換。由於每次外層循環只產生一次交換(只有一個最小值)。所以f(n)<=n 所以我們有f(n)=O(n)。所以,在數據較亂的時候,可以減少一定的交換次數。
4.插入法:
插入法較為復雜,它的基本工作原理是抽出牌,在前面的牌中尋找相應的位置插入,然後繼續下一張
#include <iostream.h>
void InsertSort(int* pData,int Count)
{
int iTemp;
int iPos;
for(int i=1;i<Count;i++)
{
iTemp = pData[i];
iPos = i-1;
while((iPos>=0) && (iTemp<pData[iPos]))
{
pData[iPos+1] = pData[iPos];
iPos--;
}
pData[iPos+1] = iTemp;
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
InsertSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
倒序(最糟情況)
第一輪:10,9,8,7->9,10,8,7(交換1次)(循環1次)
第二輪:9,10,8,7->8,9,10,7(交換1次)(循環2次)
第一輪:8,9,10,7->7,8,9,10(交換1次)(循環3次)
循環次數:6次
交換次數:3次
其他:
第一輪:8,10,7,9->8,10,7,9(交換0次)(循環1次)
第二輪:8,10,7,9->7,8,10,9(交換1次)(循環2次)
第一輪:7,8,10,9->7,8,9,10(交換1次)(循環1次)
循環次數:4次
交換次數:2次
上面結尾的行為分析事實上造成了一種假象,讓我們認為這種演算法是簡單演算法中最好的,其實不是, 因為其循環次數雖然並不固定,我們仍可以使用O方法。從上面的結果可以看出,循環的次數f(n)<= 1/2*n*(n-1)<=1/2*n*n。所以其復雜度仍為O(n*n)(這里說明一下,其實如果不是為了展示這些簡單 排序的不同,交換次數仍然可以這樣推導)。現在看交換,從外觀上看,交換次數是O(n)(推導類似 選擇法),但我們每次要進行與內層循環相同次數的『=』操作。正常的一次交換我們需要三次『=』 而這里顯然多了一些,所以我們浪費了時間。
最終,我個人認為,在簡單排序演算法中,選擇法是最好的。
二、高級排序演算法:
高級排序演算法中我們將只介紹這一種,同時也是目前我所知道(我看過的資料中)的最快的。 它的工作看起來仍然象一個二叉樹。首先我們選擇一個中間值middle程序中我們使用數組中間值,然後 把比它小的放在左邊,大的放在右邊(具體的實現是從兩邊找,找到一對後交換)。然後對兩邊分別使 用這個過程(最容易的方法——遞歸)。
1.快速排序:
#include <iostream.h>
void run(int* pData,int left,int right)
{
int i,j;
int middle,iTemp;
i = left;
j = right;
middle = pData[(left+right)/2]; //求中間值
do{
while((pData[i]<middle) && (i<right))//從左掃描大於中值的數
i++;
while((pData[j]>middle) && (j>left))//從右掃描大於中值的數
j--;
if(i<=j)//找到了一對值
{
//交換
iTemp = pData[i];
pData[i] = pData[j];
pData[j] = iTemp;
i++;
j--;
}
}while(i<=j);//如果兩邊掃描的下標交錯,就停止(完成一次)
//當左邊部分有值(left<j),遞歸左半邊
if(left<j)
run(pData,left,j);
//當右邊部分有值(right>i),遞歸右半邊
if(right>i)
run(pData,i,right);
}
void QuickSort(int* pData,int Count)
{
run(pData,0,Count-1);
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
QuickSort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
這里我沒有給出行為的分析,因為這個很簡單,我們直接來分析演算法:首先我們考慮最理想的情況
1.數組的大小是2的冪,這樣分下去始終可以被2整除。假設為2的k次方,即k=log2(n)。
2.每次我們選擇的值剛好是中間值,這樣,數組才可以被等分。
第一層遞歸,循環n次,第二層循環2*(n/2)......
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
所以演算法復雜度為O(log2(n)*n)
其他的情況只會比這種情況差,最差的情況是每次選擇到的middle都是最小值或最大值,那麼他將變 成交換法(由於使用了遞歸,情況更糟)。但是你認為這種情況發生的幾率有多大??呵呵,你完全 不必擔心這個問題。實踐證明,大多數的情況,快速排序總是最好的。 如果你擔心這個問題,你可以使用堆排序,這是一種穩定的O(log2(n)*n)演算法,但是通常情況下速度要慢 於快速排序(因為要重組堆)。
三、其他排序
1.雙向冒泡:
通常的冒泡是單向的,而這里是雙向的,也就是說還要進行反向的工作。 代碼看起來復雜,仔細理一下就明白了,是一個來回震盪的方式。 寫這段代碼的作者認為這樣可以在冒泡的基礎上減少一些交換(我不這么認為,也許我錯了)。 反正我認為這是一段有趣的代碼,值得一看。
#include <iostream.h>
void Bubble2Sort(int* pData,int Count)
{
int iTemp;
int left = 1;
int right =Count -1;
int t;
do {
//正向的部分
for(int i=right;i>=left;i--)
{
if(pData[i]<pData[i-1])
{
iTemp = pData[i];
pData[i] = pData[i-1];
pData[i-1] = iTemp;
t = i;
}
}
left = t+1;
//反向的部分
for(i=left;i<right+1;i++)
{
if(pData[i]<pData[i-1])
{
iTemp = pData[i];
pData[i] = pData[i-1];
pData[i-1] = iTemp;
t = i;
}
}
right = t-1;
}while(left<=right);
}
void main()
{
int data[] = {10,9,8,7,6,5,4};
Bubble2Sort(data,7);
for (int i=0;i<7;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
2.SHELL排序
這個排序非常復雜,看了程序就知道了。 首先需要一個遞減的步長,這里我們使用的是9、5、3、1(最後的步長必須是1)。 工作原理是首先對相隔9-1個元素的所有內容排序,然後再使用同樣的方法對相隔5-1個元素的排序,以次類推。
#include <iostream.h>
void ShellSort(int* pData,int Count)
{
int step[4];
step[0] = 9;
step[1] = 5;
step[2] = 3;
step[3] = 1;
int i,Temp;
int k,s,w;
for(int i=0;i<4;i++)
{
k = step[i];
s = -k;
for(int j=k;j<Count;j++)
{
iTemp = pData[j];
w = j-k;//求上step個元素的下標
if(s ==0)
{
s = -k;
s++;
pData[s] = iTemp;
}
while((iTemp<pData[w]) && (w>=0) && (w<=Count))
{
pData[w+k] = pData[w];
w = w-k;
}
pData[w+k] = iTemp;
}
}
}
void main()
{
int data[] = {10,9,8,7,6,5,4,3,2,1,-10,-1};
ShellSort(data,12);
for (int i=0;i<12;i++)
cout<<data[i]<<" ";
cout<<"\n";
}
呵呵,程序看起來有些頭疼。不過也不是很難,把s==0的塊去掉就輕松多了,這里是避免使用0 步長造成程序異常而寫的代碼。這個代碼我認為很值得一看。 這個演算法的得名是因為其發明者的名字D.L.SHELL。依照參考資料上的說法:「由於復雜的數學原因 避免使用2的冪次步長,它能降低演算法效率。」另外演算法的復雜度為n的1.2次冪。同樣因為非常復雜並 「超出本書討論范圍」的原因(我也不知道過程),我們只有結果了
10. 使用C語言或C++設計實現一個演算法,用以對兩個非遞減有序表A、B進行合並
直接插入排序就能完成,將其中一個順序表中的每個元素按直接插入排序的演算法依次插入另一個順序表中