生活随笔
收集整理的這篇文章主要介紹了
10种排序算法总结
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
10種排序算法總結(jié)
排序算法有很多,所以在特定情景中使用哪一種算法很重要。為了選擇合適的算法,可以按照建議的順序考慮以下標(biāo)準(zhǔn):?
(1)執(zhí)行時(shí)間?
(2)存儲(chǔ)空間?
(3)編程工作?
?? 對(duì)于數(shù)據(jù)量較小的情形,(1)(2)差別不大,主要考慮(3);而對(duì)于數(shù)據(jù)量大的,(1)為首要。?
??
主要排序法有:?
一、冒泡(Bubble)排序——相鄰交換?
二、選擇排序——每次最小/大排在相應(yīng)的位置?
三、插入排序——將下一個(gè)插入已排好的序列中?
四、殼(Shell)排序——縮小增量?
五、歸并排序?
六、快速排序?
七、堆排序?
八、拓?fù)渑判?
九、錦標(biāo)賽排序?
十、基數(shù)排序?
??
??
?
一、冒泡(Bubble)排序?
?
void BubbleSortArray()
{ for(
int i=
1;i<n;i++
) { for(
int j=
0;i<n-i;j++
) { if(a[j]>a[j+
1])
//比較交換相鄰元素
{ int temp; temp=a[j];
a[j]=a[j+
1];
a[j+
1]=
temp; } } }
} 效率 O(n2),適用于排序小列表。?
??
??
二、選擇排序?
void SelectSortArray()
{ int min_index; for(
int i=
0;i<n-
1;i++
) { min_index=
i; for(
int j=i+
1;j<n;j++)
//每次掃描選擇最小項(xiàng) if(arr[j]<arr[min_index])
min_index=
j; if(min_index!=i)
//找到最小項(xiàng)交換,即將這一項(xiàng)移到列表中的正確位置
{ int temp; temp=arr[i];
arr[i]=arr[min_index];
arr[min_index]=
temp; } }
} ?
效率O(n2),適用于排序小的列表。?
??
??
三、插入排序?
void InsertSortArray()
{ for(
int i=
1;i<n;i++)
//循環(huán)從第二個(gè)數(shù)組元素開(kāi)始,因?yàn)閍rr[0]作為最初已排序部分
{ int temp=arr[i];
//temp標(biāo)記為未排序第一個(gè)元素 int j=i-
1; while (j>=
0 && arr[j]>temp)
/*將temp與已排序元素從小到大比較,尋找temp應(yīng)插入的位置*/ { arr[j+
1]=
arr[j]; j--
; } arr[j+
1]=
temp; }
} 最佳效率O(n);最糟效率O(n2)與冒泡、選擇相同,適用于排序小列表?
若列表基本有序,則插入排序比冒泡、選擇更有效率。?
??
??
四、殼(Shell)排序——縮小增量排序?
void ShellSortArray()
{ for(
int incr=
3;incr<
0;incr--)
//增量遞減,以增量3,2,1為例
{ for(
int L=
0;L<(n-
1)/incr;L++)
//重復(fù)分成的每個(gè)子列表
{ for(
int i=L+incr;i<n;i+=incr)
//對(duì)每個(gè)子列表應(yīng)用插入排序
{ int temp=
arr[i]; int j=i-
incr; while(j>=
0&&arr[j]>
temp) { arr[j+incr]=
arr[j]; j-=
incr; } arr[j+incr]=
temp; } } }
} 適用于排序小列表。?
效率估計(jì)O(nlog2^n)~O(n^1.5),取決于增量值的最初大小。建議使用質(zhì)數(shù)作為增量值,因?yàn)槿绻隽恐凳?的冪,則在下一個(gè)通道中會(huì)再次比較相同的元素。?
殼(Shell)排序改進(jìn)了插入排序,減少了比較的次數(shù)。是不穩(wěn)定的排序,因?yàn)榕判蜻^(guò)程中元素可能會(huì)前后跳躍。?
??
??
五、歸并排序?
void MergeSort(
int low,
int high)
{ if(low>=high)
return;
//每個(gè)子列表中剩下一個(gè)元素時(shí)停止 else int mid=(low+high)/
2;
/*將列表劃分成相等的兩個(gè)子列表,若有奇數(shù)個(gè)元素,則在左邊子列表大于右側(cè)子列表*/ MergeSort(low,mid);//子列表進(jìn)一步劃分 MergeSort(mid+
1,high); int [] B=
new int [high-low+
1];
//新建一個(gè)數(shù)組,用于存放歸并的元素 for(
int i=low,j=mid+
1,k=low;i<=mid && j<=high;k++)
/*兩個(gè)子列表進(jìn)行排序歸并,直到兩個(gè)子列表中的一個(gè)結(jié)束*/ { if (arr[i]<=
arr[j];) { B[k]=
arr[i]; I++
; } else{ B[k]=arr[j]; j++
; } } for( ;j<=high;j++,k++)
//如果第二個(gè)子列表中仍然有元素,則追加到新列表 B[k]=
arr[j]; for( ;i<=mid;i++,k++)
//如果在第一個(gè)子列表中仍然有元素,則追加到新列表中 B[k]=
arr[i]; for(
int z=
0;z<high-low+
1;z++)
//將排序的數(shù)組B的 所有元素復(fù)制到原始數(shù)組arr中 arr[z]=
B[z];
} 效率O(nlogn),歸并的最佳、平均和最糟用例效率之間沒(méi)有差異。?
適用于排序大列表,基于分治法。?
??
六、快速排序?
/*快速排序的算法思想:選定一個(gè)樞紐元素,對(duì)待排序序列進(jìn)行分割,分割之后的序列一個(gè)部分小于樞紐元素,一個(gè)部分大于樞紐元素,再對(duì)這兩個(gè)分割好的子序列進(jìn)行上述的過(guò)程。*/ void swap(
int a,
int b){
int t;t =a ;a =b ;b =
t ;}
int Partition(
int [] arr,
int low,
int high)
{ int pivot=arr[low];
//采用子序列的第一個(gè)元素作為樞紐元素 while (low <
high) { //從后往前栽后半部分中尋找第一個(gè)小于樞紐元素的元素 while (low < high && arr[high] >=
pivot) { --
high; } //將這個(gè)比樞紐元素小的元素交換到前半部分
swap(arr[low], arr[high]); //從前往后在前半部分中尋找第一個(gè)大于樞紐元素的元素 while (low <high &&arr [low ]<=
pivot ) { ++
low ; } swap (arr [low ],arr [high ]);//將這個(gè)樞紐元素大的元素交換到后半部分
} return low ;
//返回樞紐元素所在的位置
}
void QuickSort(
int [] a,
int low,
int high)
{ if (low <
high ) { int n=
Partition (a ,low ,high ); QuickSort (a ,low ,n ); QuickSort (a ,n +
1,high ); }
} 平均效率O(nlogn),適用于排序大列表。?
此算法的總時(shí)間取決于樞紐值的位置;選擇第一個(gè)元素作為樞紐,可能導(dǎo)致O(n2)的最糟用例效率。若數(shù)基本有序,效率反而最差。選項(xiàng)中間值作為樞紐,效率是O(nlogn)。?
基于分治法。?
??
??
?
七、堆排序?
最大堆:后者任一非終端節(jié)點(diǎn)的關(guān)鍵字均大于或等于它的左、右孩子的關(guān)鍵字,此時(shí)位于堆頂?shù)墓?jié)點(diǎn)的關(guān)鍵字是整個(gè)序列中最大的。?
思想:?
(1)令i=l,并令temp= kl ;?
(2)計(jì)算i的左孩子j=2i+1;?
(3)若j<=n-1,則轉(zhuǎn)(4),否則轉(zhuǎn)(6);?
(4)比較kj和kj+1,若kj+1>kj,則令j=j+1,否則j不變;?
(5)比較temp和kj,若kj>temp,則令ki等于kj,并令i=j,j=2i+1,并轉(zhuǎn)(3),否則轉(zhuǎn)(6)?
(6)令ki等于temp,結(jié)束。?
void HeapSort(SeqIAst R)
{ //對(duì)R[1..n]進(jìn)行堆排序,不妨用R[0]做暫存單元 int I; BuildHeap(R);//將R[1-n]建成初始堆for(i=n;i>
1;i--)
//對(duì)當(dāng)前無(wú)序區(qū)R[1..i]進(jìn)行堆排序,共做n-1趟。
{ R[0]=R[
1];R[1]=
R[i];R[i]=R[
0];
//將堆頂和堆中最后一個(gè)記錄交換 Heapify(R,
1,i-
1);
//將R[1..i-1]重新調(diào)整為堆,僅有R[1]可能違反堆性質(zhì)
}
} 堆排序的時(shí)間,主要由建立初始堆和反復(fù)重建堆這兩部分的時(shí)間開(kāi)銷(xiāo)構(gòu)成,它們均是通過(guò)調(diào)用Heapify實(shí)現(xiàn)的。?
?
????? 堆排序的最壞時(shí)間復(fù)雜度為O(nlgn)。堆排序的平均性能較接近于最壞性能。???? 由于建初始堆所需的比較次數(shù)較多,所以堆排序不適宜于記錄數(shù)較少的文件。???? 堆排序是就地排序,輔助空間為O(1),???? 它是不穩(wěn)定的排序方法。?
?
??
堆排序與直接插入排序的區(qū)別:?
???? 直接選擇排序中,為了從R[1..n]中選出關(guān)鍵字最小的記錄,必須進(jìn)行n-1次比較,然后在R[2..n]中選出關(guān)鍵字最小的記錄,又需要做n-2次比較。事實(shí)上,后面的n-2次比較中,有許多比較可能在前面的n-1次比較中已經(jīng)做過(guò),但由于前一趟排序時(shí)未保留這些比較結(jié)果,所以后一趟排序時(shí)又重復(fù)執(zhí)行了這些比較操作。?
???? 堆排序可通過(guò)樹(shù)形結(jié)構(gòu)保存部分比較結(jié)果,可減少比較次數(shù)。?
??
?
八、拓?fù)渑判?
例 :學(xué)生選修課排課先后順序?
拓?fù)渑判?#xff1a;把有向圖中各頂點(diǎn)按照它們相互之間的優(yōu)先關(guān)系排列成一個(gè)線(xiàn)性序列的過(guò)程。?
方法:?
在有向圖中選一個(gè)沒(méi)有前驅(qū)的頂點(diǎn)且輸出?
從圖中刪除該頂點(diǎn)和所有以它為尾的弧?
重復(fù)上述兩步,直至全部頂點(diǎn)均已輸出(拓?fù)渑判虺晒?#xff09;,或者當(dāng)圖中不存在無(wú)前驅(qū)的頂點(diǎn)(圖中有回路)為止。?
void TopologicalSort()
/*輸出拓?fù)渑判蚝瘮?shù)。若G無(wú)回路,則輸出G的頂點(diǎn)的一個(gè)拓?fù)湫蛄胁⒎祷豋K,否則返回ERROR*/
{ int indegree[M]; int i,k,j; char n; int count=
0; Stack thestack; FindInDegree(G,indegree);//對(duì)各頂點(diǎn)求入度indegree[0....num] InitStack(thestack);
//初始化棧 for(i=
0;i<G.num;i++
) Console.WriteLine("結(jié)點(diǎn)"+G.vertices[i].data+
"的入度為"+
indegree[i]); for(i=
0;i<G.num;i++
) { if(indegree[i]==
0) Push(thestack.vertices[i]); } Console.Write("拓?fù)渑判蜉敵鲰樞驗(yàn)?#xff1a;"); while(thestack.Peek()!=
null) { Pop(thestack.Peek()); j=
locatevex(G,n); if (j==-
2) { Console.WriteLine("發(fā)生錯(cuò)誤,程序結(jié)束。"); exit(); } Console.Write(G.vertices[j].data); count++
; for(p=G.vertices[j].firstarc;p!=NULL;p=
p.nextarc) { k=
p.adjvex; if (!(--
indegree[k])) Push(G.vertices[k]); } } if (count<
G.num) Cosole.WriteLine("該圖有環(huán),出現(xiàn)錯(cuò)誤,無(wú)法排序。"); elseConsole.WriteLine("排序成功。");
} 算法的時(shí)間復(fù)雜度O(n+e)。?
??
??
?
九、錦標(biāo)賽排序?
錦標(biāo)賽排序的算法思想與體育比賽類(lèi)似。?
??? 首先將n個(gè)數(shù)據(jù)元素兩兩分組,分別按關(guān)鍵字進(jìn)行比較,得到n/2個(gè)比較的優(yōu)勝者(關(guān)鍵字小者),作為第一步比較的結(jié)果保留下來(lái),?
??? 然后對(duì)這n/2個(gè)數(shù)據(jù)元素再兩兩分組,分別按關(guān)鍵字進(jìn)行比較,…,如此重復(fù),直到選出一個(gè)關(guān)鍵字最小的數(shù)據(jù)元素為止。?
#include <stdio.h>
#include <stdlib.h>
#include <
string.h>
#include <math.h>
#define SIZE 100000
#define MAX 1000000
struct node
{ long num;
//關(guān)鍵字 char str[
10]; int lastwin;
//最后勝的對(duì)手 int killer;
//被擊敗的對(duì)手 long times;
//比賽次數(shù)
}data[SIZE];
long CompareNum=
0;
long ExchangeNum=
0;
long Read(
char name[])
//讀取文件a.txt中的數(shù)據(jù),并存放在數(shù)組data[]中;最后返回?cái)?shù)據(jù)的個(gè)數(shù)
{ FILE *
fp; long i=
1; fp=fopen(name,
"rw"); fscanf(fp,"%d%s",&
data[i].num,data[i].str); while(!
feof(fp)) { i++
; fscanf(fp,"%d%s",&
data[i].num,data[i].str); } return (i-
1);
}
long Create(
long num)
//創(chuàng)建勝者樹(shù),返回冠軍(最小數(shù))在數(shù)組data[]中的下標(biāo)
{ int i,j1,j2,max,time=
1; long min;
//記錄當(dāng)前冠軍的下標(biāo) for(i=
1;pow(
2,i-
1)<num;i++
) ; max=pow(
2,i-
1);
//求葉子結(jié)點(diǎn)數(shù)目 for(i=
1;i<=max;i++)
//初始化葉子結(jié)點(diǎn)
{ data[i].killer=
0; data[i].lastwin=
0; data[i].times=
0; if(i>
num) data[i].num=
MAX; } for(i=
1;i<=max;i+=
2)
//第一輪比賽
{ ++
CompareNum; if(data[i].num <= data[i+
1].num) { data[i].lastwin = i+
1; data[i+
1].killer=
i; ++
data[i].times; ++data[i+
1].times; min=
i; } else{ data[i+
1].lastwin=
i; data[i].killer=i+
1; ++
data[i].times; ++data[i+
1].times; min=i+
1; } } j1=j2=
0;
//記錄連續(xù)的兩個(gè)未被淘汰的選手的下標(biāo) while(time <= (log(max)/log(
2)))
//進(jìn)行淘汰賽
{ for(i=
1;i<=max;i++
) { if(data[i].times==time && data[i].killer==
0)
//找到一名選手
{ j2=i;
//默認(rèn)其為兩選手中的后來(lái)的 if(j1==
0)
//如果第一位置是空的,則剛來(lái)的選手先來(lái)的 j1=
j2; else//否則剛來(lái)的選手是后來(lái)的,那么選手都已到場(chǎng)比賽開(kāi)始
{ ++
CompareNum; if(data[j1].num <= data[j2].num)
//先來(lái)的選手獲勝
{ data[j1].lastwin = j2;
//最后贏(yíng)的是j2 data[j2].killer=j1;
//j2是被j1淘汰的 ++
data[j1].times; ++data[j2].times;
//兩選手場(chǎng)次均加1 min=j1;
//最小數(shù)下標(biāo)為j1 j1=j2=
0;
//將j1,j2置0
} else//同理
{ data[j2].lastwin=
j1; data[j1].killer=
j2; ++
data[j1].times; ++
data[j2].times; min=
j2; j1=j2=
0; } } } } time++;
//輪數(shù)加1
} return min;
//返回冠軍的下標(biāo)
}
void TournamentSort(
long num)
//錦標(biāo)賽排序
{ long tag=Create(num);
//返回最小數(shù)下標(biāo) FILE *
fp1; fp1=fopen(
"sort.txt",
"w+");
//為寫(xiě)入創(chuàng)建并打開(kāi)文件sort.txt while(data[tag].num != MAX)
//當(dāng)最小值不是無(wú)窮大時(shí)
{ printf("%d %s\n",data[tag].num,data[tag].str);
//輸出數(shù)據(jù) fprintf(fp1,
"%d %s\n",data[tag].num,data[tag].str);
//寫(xiě)入數(shù)據(jù) data[tag].num=MAX;
//將當(dāng)前冠軍用無(wú)窮大替換 tag=Create(num);
//返回下一個(gè)冠軍的下標(biāo)
}
}
int main()
{ int num; char name[
10]; printf("Input name of the file:"); gets(name); num=Read(name);
//讀文件 TournamentSort(num);
//錦標(biāo)賽排序 printf(
"CompareNum=%d\nExchangeNum=%d\n",CompareNum,ExchangeNum); return 0;
}
十、基數(shù)排序?
基數(shù)排序又被稱(chēng)為桶排序。與前面介紹的幾種排序方法相比較,基數(shù)排序和它們有明顯的不同。?
??? 前面所介紹的排序方法都是建立在對(duì)數(shù)據(jù)元素關(guān)鍵字進(jìn)行比較的基礎(chǔ)上,所以可以稱(chēng)為基于比較的排序;?
??? 而基數(shù)排序首先將待排序數(shù)據(jù)元素依次“分配”到不同的桶里,然后再把各桶中的數(shù)據(jù)元素“收集”到一起。?
通過(guò)使用對(duì)多關(guān)鍵字進(jìn)行排序的這種“分配”和“收集”的方法,基數(shù)排序?qū)崿F(xiàn)了對(duì)多關(guān)鍵字進(jìn)行排序。?
———————————————————————————————————————?
例:?
??? 每張撲克牌有兩個(gè)“關(guān)鍵字”:花色和面值。其大小順序?yàn)?#xff1a;?
??? 花色:§<¨<?<a?
??? 面值:2<3<……<K<A?
??? 撲克牌的大小先根據(jù)花色比較,花色大的牌比花色小的牌大;花色一樣的牌再根據(jù)面值比較大小。所以,將撲克牌按從小到大的次序排列,可得到以下序列:?
?§2,…,§A,¨2,…,¨A,?2,…,?A,a2,…,aA?
??? 這種排序相當(dāng)于有兩個(gè)關(guān)鍵字的排序,一般有兩種方法實(shí)現(xiàn)。?
??? 其一:可以先按花色分成四堆(每一堆牌具有相同的花色),然后在每一堆牌里再按面值從小到大的次序排序,最后把已排好序的四堆牌按花色從小到大次序疊放在一起就得到排序的結(jié)果。?
其二:可以先按面值排序分成十三堆(每一堆牌具有相同的面值),然后將這十三堆牌按面值從小到大的順序疊放在一起,再把整副牌按順序根據(jù)花色再分成四堆(每一堆牌已按面值從小到大的順序有序),最后將這四堆牌按花色從小到大合在一起就得到排序的結(jié)果。?
———————————————————————————————————————?
實(shí)現(xiàn)方法:?
最高位優(yōu)先(Most Significant Digit first)法,簡(jiǎn)稱(chēng)MSD法:先按k1排序分組,同一組中記錄,關(guān)鍵碼k1相等,再對(duì)各組按k2排序分成子組,之后,對(duì)后面的關(guān)鍵碼繼續(xù)這樣的排序分組,直到按最次位關(guān)鍵碼kd對(duì)各子組排序后。再將各組連接起來(lái),便得到一個(gè)有序序列。?
最低位優(yōu)先(Least Significant Digit first)法,簡(jiǎn)稱(chēng)LSD法:先從kd開(kāi)始排序,再對(duì)kd-1進(jìn)行排序,依次重復(fù),直到對(duì)k1排序后便得到一個(gè)有序序列。?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LearnSort
{ class Program { static void Main(
string[] args) { int[] arr = CreateRandomArray(
10);
//產(chǎn)生隨機(jī)數(shù)組 Print(arr);
//輸出數(shù)組 RadixSort(
ref arr);
//排序 Print(arr);
//輸出排序后的結(jié)果
Console.ReadKey(); } public static void RadixSort(
ref int[] arr) { int iMaxLength =
GetMaxLength(arr); RadixSort(ref arr, iMaxLength); } private static void RadixSort(
ref int[] arr,
int iMaxLength) { List<
int> list =
new List<
int>();
//存放每次排序后的元素 List<
int>[] listArr =
new List<
int>[
10];
//十個(gè)桶 char currnetChar;
//存放當(dāng)前的字符比如說(shuō)某個(gè)元素123 中的2 string currentItem;
//存放當(dāng)前的元素比如說(shuō)某個(gè)元素123 for (
int i =
0; i < listArr.Length; i++)
//給十個(gè)桶分配內(nèi)存初始化。 listArr[i] =
new List<
int>
(); for (
int i =
0; i < iMaxLength; i++)
//一共執(zhí)行iMaxLength次,iMaxLength是元素的最大位數(shù)。
{ foreach (
int number
in arr)
//分桶
{ currentItem = number.ToString();
//將當(dāng)前元素轉(zhuǎn)化成字符串 try { currnetChar = currentItem[currentItem.Length-i-
1]; }
//從個(gè)位向高位開(kāi)始分桶 catch { listArr[
0].Add(number);
continue; }
//如果發(fā)生異常,則將該數(shù)壓入listArr[0]。比如說(shuō)5 是沒(méi)有十位數(shù)的,執(zhí)行上面的操作肯定會(huì)發(fā)生越界異常的,這正是期望的行為,我們認(rèn)為5的十位數(shù)是0,所以將它壓入listArr[0]的桶里。 switch (currnetChar)
//通過(guò)currnetChar的值,確定它壓人哪個(gè)桶中。
{ case '0': listArr[
0].Add(number);
break; case '1': listArr[
1].Add(number);
break; case '2': listArr[
2].Add(number);
break; case '3': listArr[
3].Add(number);
break; case '4': listArr[
4].Add(number);
break; case '5': listArr[
5].Add(number);
break; case '6': listArr[
6].Add(number);
break; case '7': listArr[
7].Add(number);
break; case '8': listArr[
8].Add(number);
break; case '9': listArr[
9].Add(number);
break; default:
throw new Exception(
"unknow error"); } } for (
int j =
0; j < listArr.Length; j++)
//將十個(gè)桶里的數(shù)據(jù)重新排列,壓入list foreach (
int number
in listArr[j].ToArray<
int>
()) { list.Add(number); listArr[j].Clear();//清空每個(gè)桶
} arr = list.ToArray<
int>();
//arr指向重新排列的元素 //Console.Write("{0} times:",i); Print(arr);
//輸出一次排列的結(jié)果 list.Clear();
//清空l(shuí)ist
} } //得到最大元素的位數(shù) private static int GetMaxLength(
int[] arr) { int iMaxNumber =
Int32.MinValue; foreach (
int i
in arr)
//遍歷得到最大值
{ if (i >
iMaxNumber) iMaxNumber =
i; } return iMaxNumber.ToString().Length;
//這樣獲得最大元素的位數(shù)是不是有點(diǎn)投機(jī)取巧了...
} //輸出數(shù)組元素 public static void Print(
int[] arr) { foreach (
int i
in arr) System.Console.Write(i.ToString()+
'\t'); System.Console.WriteLine(); } //產(chǎn)生隨機(jī)數(shù)組。隨機(jī)數(shù)的范圍是0到1000。參數(shù)iLength指產(chǎn)生多少個(gè)隨機(jī)數(shù) public static int[] CreateRandomArray(
int iLength) { int[] arr =
new int[iLength]; Random random =
new Random(); for (
int i =
0; i < iLength; i++
) arr[i] = random.Next(
0,
1001); return arr; } }
} 基數(shù)排序法是屬于穩(wěn)定性的排序,其時(shí)間復(fù)雜度為O (nlog(r)m),其中r為所采取的基數(shù),而m為堆數(shù),在某些時(shí)候,基數(shù)排序法的效率高于其它的比較性排序法。
轉(zhuǎn)載于:https://www.cnblogs.com/Dageking/archive/2013/04/14/3020224.html
總結(jié)
以上是生活随笔為你收集整理的10种排序算法总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。