排序作為計(jì)算機(jī)程序設(shè)計(jì)中的一種重要運(yùn)算,在實(shí)際中應(yīng)用很廣耗帕,據(jù)-統(tǒng)計(jì)穆端,計(jì)算機(jī)處理的25%的機(jī)時(shí)是用于排序的。
1仿便、排序的分類
根據(jù)排序中所涉及的存儲(chǔ)器体啰,可將排序分為內(nèi)部排序和外部排序兩大類
1、內(nèi)部排序:排序過(guò)程中嗽仪,所有記錄都放在內(nèi)存中處理的是內(nèi)部排序荒勇。
2、外部排序:當(dāng)待排序的記錄太多闻坚,排序是不僅要使用內(nèi)存沽翔,而且還要使用外部存儲(chǔ)器的排序方法是外部排序。
2窿凤、排序算法的性能分析
- 穩(wěn)定性
1仅偎、穩(wěn)定:如果待排序的記錄中,存在多個(gè)關(guān)鍵字相同的記錄雳殊,經(jīng)排序后這些記錄的相對(duì)次序仍然保持不變橘沥。
2、不穩(wěn)定:如果待排序的值中夯秃,存在多個(gè)關(guān)鍵字相同的值威恼,經(jīng)排序后這些值的相對(duì)次序發(fā)生改變品姓,稱為不穩(wěn)定的排序。 - 時(shí)間復(fù)雜度
1箫措、 時(shí)間復(fù)雜度可以認(rèn)為是對(duì)排序數(shù)據(jù)的總的操作次數(shù)腹备。反映當(dāng)n變化時(shí),操作次數(shù)呈現(xiàn)什么規(guī)律斤蔓。
2植酥、常見(jiàn)的時(shí)間復(fù)雜度有:常數(shù)階O(1),對(duì)數(shù)階O(log2n),線性階O(n), 線性對(duì)數(shù)階O(nlog2n),平方階O(n2)。
3弦牡、時(shí)間復(fù)雜度O(1):算法中語(yǔ)句執(zhí)行次數(shù)為一個(gè)常數(shù)友驮,則時(shí)間復(fù)雜度為O(1)。 - 空間復(fù)雜度
1驾锰、算法在計(jì)算機(jī)內(nèi)執(zhí)行時(shí)所需存儲(chǔ)空間的度量卸留,它也是問(wèn)題規(guī)模n的函數(shù)。
2椭豫、空間復(fù)雜度O(1):當(dāng)一個(gè)算法的空間復(fù)雜度為一個(gè)常量耻瑟,即不隨被處理數(shù)據(jù)量n的大小而改變時(shí),可表示為O(1)赏酥。
3喳整、空間復(fù)雜度O(log2N):當(dāng)一個(gè)算法的空間復(fù)雜度與以2為底的n的對(duì)數(shù)成正比時(shí),可表示為O(log2n) ax=N裸扶,則x=logaN框都。
4、空間復(fù)雜度O(n):當(dāng)一個(gè)算法的空間復(fù)雜度與n成線性比例關(guān)系時(shí)呵晨,可表示為0(n)魏保。 -
復(fù)雜度分析:
3、排序算法
3.1摸屠、冒泡排序(Bubble Sort)
基本思路:從R1開(kāi)始囱淋,兩兩比較相鄰值的關(guān)鍵字,即比較Ri和Ri+1(i=1餐塘,2妥衣,...,n-1)的關(guān)鍵字大小戒傻,若逆序(如Ki > Ki+1),則交換Ri 和Ri+1的位置税手,如此經(jīng)過(guò)一趟排序,關(guān)鍵字最大的記錄被安置在最后一個(gè)位置(Rn)上需纳。然后再對(duì)前n-1個(gè)記錄進(jìn)行同樣的操作芦倒,具有次大關(guān)鍵字的值被安置在Rn-1上。如此反復(fù)不翩。
void BobbleSort(int array[],int N){
bool bExchangeFlag;
for (int i = 1; i < N; i++) { //最多做length-1趟排序
bExchangeFlag = false; //
for (int j = 0; j < N - i; j++) {
if (array[j] > array[j+1]) {
swap(&array[i], &array[j+1]);
bExchangeFlag = true;
}
}
if (!bExchangeFlag) {//本趟排序未發(fā)生交換兵扬,則提前終止算法
break;
}
}
}
3.1.1麻裳、性能分析
穩(wěn)定性:不穩(wěn)定
分類:內(nèi)排序
時(shí)間復(fù)雜度
(1)最好情況:O(n)--正好是正序的,不需要交換數(shù)據(jù)
(2)最壞情況:O--逆序
(3)平均復(fù)雜度:O(n)
(4)空間復(fù)雜度:O(1)
3.2器钟、插入排序(Insertion Sort)
基本思路:構(gòu)建有序序列津坑,對(duì)于未排序數(shù)據(jù),在已排序中從后向前掃描傲霸,找到相應(yīng)位置插入疆瑰,直到將未排序隊(duì)列過(guò)濾完畢。
排序過(guò)程如下:
void Insertion_Sort(int array[],int N){
for (int p = 1; p < N; p++) {
int temp = array[p];
int I;
for (i = p; p > 0 && array[i-1] > temp; i--) {
array[i] = array[i-1];
}
array[i] = temp;
}
}
3.2.1昙啄、性能分析
穩(wěn)定性:穩(wěn)定
分類:內(nèi)排序
時(shí)間復(fù)雜度
(1)最好情況:O(n)--當(dāng)待排序數(shù)組是有序時(shí)
(2)最壞情況:O)--待排序數(shù)組是逆序時(shí)
(3)平均復(fù)雜度:O(n)
(4)空間復(fù)雜度:O(1)
3.3穆役、希爾排序(Shell Insertion Sort)
基本思路:定義增量序列 > >...> = 1,對(duì)每個(gè)進(jìn)行“間隔”插入排序。
注意:“間隔”有序的序列梳凛,在執(zhí)行“間隔”排序后耿币,仍然是“間隔”有序的。
排序過(guò)程如下:
#include<stdio.h>
#include <math.h>
#define MAXNUM 10
//根據(jù)當(dāng)前增量進(jìn)行插入排序
void shellInsert(int array[],int n,int dk)
{
int i,j,temp;
for(i=dk;i<n;i++)//分別向每組的有序區(qū)域插入
{
temp=array[I];
for(j=i-dk;(j>=i%dk)&&array[j]>temp;j-=dk)//比較與記錄后移同時(shí)進(jìn)行
array[j+dk]=array[j];
if(j!=i-dk)
array[j+dk]=temp;//插入
}
}
//計(jì)算Hibbard增量
int dkHibbard(int t,int k)
{
return (int)(pow(2,t-k+1)-1);
}
void Shell_Sort(int array[],int n,int t)
{
for(int i=1;i<=t;i++)
shellInsert(array,n,dkHibbard(t,i));
}
void ShellSort(void){
int array[] = {1,10,3,5,4,2,15,9,54,18};
Shell_Sort(array,MAXNUM,(int)(log(MAXNUM+1)/log(2)));//排序趟數(shù)應(yīng)為log2(n+1)的整數(shù)部分
for(int i = 0;i< MAXNUM;i++)
printf("%d ",array[I]);
}
再看一個(gè)序列:1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
可以看到增量元素不互質(zhì)韧拒,則小增量可能不起作用淹接,所以選擇增量序列模式很重要。
目前主流增量序列:
Hibbard 增量序列:= - 1 相鄰元素互質(zhì)
最壞情況:T = O
猜想: = O
Sedgewick 增量序列:{1,5,19,41,109,...}
9-9+1或-3+1
猜想: = O叭莫, = O
3.3.1、性能分析
穩(wěn)定性:不穩(wěn)定
分類:內(nèi)排序
時(shí)間復(fù)雜度
(1)最好情況:O()
(2)最壞情況:O()
(3)平均復(fù)雜度:O()
(4)空間復(fù)雜度:O()
3.4烁试、快速排序(Shell Insertion Sort)
基本思路:設(shè)要排序的數(shù)組是A[0]……A[N-1]雇初,首先任意選取一個(gè)數(shù)據(jù)(通常選用數(shù)組的最后一個(gè)數(shù))作為關(guān)鍵數(shù)據(jù),然后將所有比它小的數(shù)都放到它左邊减响,所有比它大的數(shù)都放到它右邊靖诗,將該數(shù)組分成2個(gè)數(shù)組,再遞歸的處理左邊數(shù)組和右邊數(shù)組支示。
排序過(guò)程如下:
int Median(int array[],int left,int right){
int center = (left + right)/2;
if (array[left] > array[center]) {
swap(&array[left], &array[center]);
}
if (array[left] > array[right]) {
swap(&array[left], &array[right]);
}
if (array[center] > array[right]) {
swap(&array[center], &array[right]);
}
swap(&array[center], &array[right - 1]);
return array[right - 1];
}
void QickSort(int array[],int left,int right){
if (1 < right-left) {
int pivot = Median(array, left, right);
int i = left;
int j = right - 1;
for (;;) {
while (array[++i] < pivot) {}
while (array[--j] > pivot) {}
if (i < j) {
swap(&array[i], &array[j]);
}else{
break;
};
}
swap(&array[i], &array[right - 1]);
QickSort(array, left, i-1);
QickSort(array, i+1, right);
}
}
void quick_sort(int array[],int N){
QickSort(array, 0, N - 1);
}
void quicksort(void){
int tree[] = {1,10,3,5,4,2,15,9,54,18};
int n = 10;
quick_sort(tree, 10);
for (int i = 0; i < n; i++) {
printf("%d ",tree[I]);
}
}
3.4.1刊橘、性能分析
穩(wěn)定性:不穩(wěn)定
分類:內(nèi)排序
時(shí)間復(fù)雜度
(1)最好情況:O(n)--當(dāng)待排序數(shù)組是有序時(shí)
(2)最壞情況:O--待排序數(shù)組是逆序時(shí)
(3)平均復(fù)雜度:O(n)
(4)空間復(fù)雜度:O(n)
3.5、直接選擇排序(Selection Sort)
基本思路:從待排序的數(shù)據(jù)元素中選擇最兴毯琛(或最大)的一個(gè)元素作為首元素促绵,把它與第一元素交換存儲(chǔ)位置,然后再在余下的元素中選出此小的嘴纺,把它與第二元素交換败晴,以此類推,直到排序完成栽渴。
排序過(guò)程如下:
void Select_Sort(int array[],int N){
int nMinIndex;//用于記錄最小元素的下標(biāo)值
for(int i = 0;i < N - 1;i++){
nMinIndex = I;
for (int j = i + 1; j < N; j++) {
if (array[j] < array[nMinIndex]) {
nMinIndex = j;
}
}
if (nMinIndex != i) {
swap(&array[nMinIndex], &array[I]);
}
}
}
3.5.1尖坤、性能分析
穩(wěn)定性:不穩(wěn)定
分類:內(nèi)排序
時(shí)間復(fù)雜度
(1)最好情況:O--當(dāng)待排序數(shù)組是有序時(shí)
(2)最壞情況:O--待排序數(shù)組是逆序時(shí)
(3)平均復(fù)雜度:O(n)
(4)空間復(fù)雜度:O(1)
3.6、歸并排序(Merge Sort)
基本思想:指的是將兩個(gè)順序序列合并成一個(gè)順序序列的排序方法闲擦。
歸并排序的實(shí)現(xiàn)方法:
3.6.1遞歸方法
//L = 左邊起始位置慢味,R= 右邊起始位置场梆,RightEnd 右邊終點(diǎn)位置
void Merge(int Array[],int TempArray[],int L,int R,int RightEnd){
int LeftEnd = R - 1;
int Temp = L; //存放結(jié)果數(shù)組的起始位置
int NumCount = RightEnd - L + 1;
while (L<=LeftEnd && R <= RightEnd) {
if (Array[L] <= Array[R]) {
TempArray[Temp++] = Array[L++];
}else{
TempArray[Temp++] = Array[R++];
}
}
while(L <= LeftEnd) { //直接復(fù)制左邊剩下的元素
TempArray[Temp++] = Array[L++];
}
while(R <= RightEnd) {//直接復(fù)制右邊剩下的元素
TempArray[Temp++] = Array[R++];
}
for(int i = 0;i < NumCount;i++,RightEnd--){
Array[RightEnd] = TempArray[RightEnd];
}
}
void MSort(int Array[],int TempArray[],int L,int RightEnd){
int Center;
if (L < RightEnd) {
Center = (L + RightEnd)/2;
MSort(Array, TempArray, L, Center);
MSort(Array, TempArray, Center+1, RightEnd);
Merge(Array, TempArray, L, Center+1, RightEnd);
}
}
void Merge_Sort(int Array[],int N){
int *tempArray = (int *)malloc(N*sizeof(int));
if (tempArray != NULL) {
MSort(Array, tempArray, 0, N-1);
}
}
3.6.2非遞歸方法
void MergePass(int Array[],int TempArray[],int L,int R,int RightEnd){
int LeftEnd = R - 1;
int Temp = L; //存放結(jié)果數(shù)組的起始位置
int NumCount = RightEnd - L + 1;
while (L<=LeftEnd && R <= RightEnd) {
if (Array[L] <= Array[R]) {
TempArray[Temp++] = Array[L++];
}else{
TempArray[Temp++] = Array[R++];
}
}
while(L <= LeftEnd) { //直接復(fù)制左邊剩下的元素
TempArray[Temp++] = Array[L++];
}
while(R <= RightEnd) {//直接復(fù)制右邊剩下的元素
TempArray[Temp++] = Array[R++];
}
}
void Merge_pass(int array[],int TempArray[],int N,int length){ //有序序列長(zhǎng)度
for (int i = 0; i < N - 2*length; i +=2*length) {
Merge(array, TempArray, i, i+length, i + 2*length - 1);
if (i + length < N) {
MergePass(array, TempArray, i, i + length, N-1);
}else{
for (int j = i; j < N; j++) {
TempArray[j] = array[j];
}
}
}
}
void Merge_no_recursion_Sort(int Array[],int N){
int length = 1;//子序列長(zhǎng)度
int *tempArray = (int *)malloc(N*sizeof(int));
if (tempArray != NULL) {
while (length < N) {
Merge_pass(Array, tempArray, N, length);
length *= 2;
Merge_pass(tempArray, Array, N, length);
length *= 2;
}
free(tempArray);
}
}
3.6.3、性能分析
性能分析:
穩(wěn)定性:穩(wěn)定
分類:內(nèi)排序
時(shí)間復(fù)雜度
(1)最好情況:O( n)
(2)最壞情況:O( n)
(3)平均復(fù)雜度:O( n)
(4)空間復(fù)雜度:O(n)
3.7纯路、計(jì)數(shù)排序(Count Sort)
計(jì)數(shù)排序是一種非比較排序算法或油,它的優(yōu)勢(shì)在于在對(duì)一定范圍內(nèi)的整數(shù)排序時(shí),它的復(fù)雜度為Ο(n+k)(其中k是整數(shù)的范圍)感昼,快于任何比較排序算法装哆。當(dāng)然這是一種犧牲空間換取時(shí)間的做法,而且當(dāng)O(k)>O(n*log(n))的時(shí)候其效率反而不如基于比較的排序定嗓。
基本思想:計(jì)數(shù)排序的基本思想就是對(duì)每一個(gè)輸入元素x蜕琴,確定小于x的元素個(gè)數(shù),我們就可以直接把x放到最終輸出數(shù)組中的相應(yīng)位置上宵溅。
排序過(guò)程:
1凌简、查詢待數(shù)組中的最大值和最小值,根據(jù)最大元素和最小元素的差值(max-min+1),申請(qǐng)額外空間恃逻。
2雏搂、遍歷待排序數(shù)組,將每一個(gè)元素出現(xiàn)次數(shù)(value)記錄到元素值(key)對(duì)應(yīng)的額外空間內(nèi)寇损。
3凸郑、從額外空間的第二個(gè)元素開(kāi)始,將當(dāng)前元素個(gè)數(shù)加上前一個(gè)元素的個(gè)數(shù)矛市,即可確定序列中值小于x的元素的個(gè)數(shù)芙沥,也就確定了元素x的位置。
4浊吏、將待排序數(shù)組的元素倒序移動(dòng)到新數(shù)組中而昨。
示例:
#include<stdlib.h>
#include<string.h>
#define MAXNUM 10
void Count_Sort(int Array[],int N){
int Max = Array[0];
int Min = Array[0];
for (int i = 1; i < N-1; i++) {
if (Array[i] >= Max) {
Max = Array[i];
}else if(Array[i] <= Min){
Min = Array[i];
}
}
int size = Max - Min +1;
int* Array_B = (int*)malloc(size*sizeof(int));
memset(Array_B, 0, sizeof(int)*size);
for (int i = 0; i < N; i++) {
Array_B[Array[i] - Min]++;
}
for (int i = 1; i < size; i++) {
Array_B[i] = Array_B[i] + Array_B[i - 1];
}
int* Array_C= (int *)malloc((N)*sizeof(int));
memset(Array_C, 0, sizeof(int)*size);
for (int i = N-1; i >= 0; i--) {
Array_B[Array[i] - Min]--;
Array_C[Array_B[Array[i] - Min]] = Array[i];
}
for (int i = 0; i < N; i++) {
Array[i] = Array_C[i];
}
free(Array_B);
free(Array_C);
Array_B = NULL;
Array_C = NULL;
}
void CountSort(void){
int array[] = {1,10,3,5,4,2,15,9,54,18};
Count_Sort(array,MAXNUM);//排序趟數(shù)應(yīng)為log2(n+1)的整數(shù)部分
for(int i = 0;i< MAXNUM;i++)
printf("%d ",array[i]);
}
3.8、基數(shù)排序(Radix Sort)
基本思想:將整數(shù)按位數(shù)切割成不同的數(shù)字找田,然后按每個(gè)位數(shù)分別比較歌憨。
排序過(guò)程:
1、所有待排序整數(shù)統(tǒng)一成位數(shù)相同的數(shù)值墩衙,位數(shù)不夠的整數(shù)需要將缺位補(bǔ)零务嫡。
2、從低位開(kāi)始比較漆改,依此進(jìn)行排序植袍。
3、從低位到高位比較完成后籽懦,待排序數(shù)組就是一個(gè)有序數(shù)組于个。
//獲取數(shù)字位數(shù)
#include <math.h>
#define MAXNUM 10
int getLoopTimes(int num){
int count = 1;
int temp = num/10;
while (temp != 0) {
count++;
temp = temp/10;
}
return count;
}
//查詢數(shù)組中的最大值
int findMaxNum(int *p,int n){
int max = 0;
for (int i = 0;i < n;i++) {
if (*(p + i) > max) {
max = *(p+i);
}
}
return max;
}
void Radix_Sort(int *p,int n,int loop){
int buckets[10][MAXNUM] = {};//建立一組桶此處的MAXNUM是預(yù)設(shè)的根據(jù)實(shí)際數(shù)情況修改,
int tempNum = (int)pow(10,loop - 1);//求整數(shù)各占位的數(shù)字
for (int i= 0; i < n; i++) {
int row_index = (*(p + i)/tempNum)%10;
for (int j = 0;j < MAXNUM;j++) {
if(buckets[row_index][j] == 0) {
buckets[row_index][j] = *(p + i);
break;
}
}
}
int k = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < MAXNUM; j++) {
if (buckets[i][j] != 0) {
*(p + k) = buckets[i][j];
buckets[i][j] = 0;
k++;
}
}
}
}
void bucketSort(int *p,int n){
int maxNum = findMaxNum(p, n);//獲取數(shù)組中的最大數(shù)
int loopTimes = getLoopTimes(maxNum);//獲取最大數(shù)的位數(shù)
for (int i = 1;i <= loopTimes;i++) {
Radix_Sort(p, n, i);
}
}
void RadixSort(){
int array[] = {512,240,666,520,43,76};
int *array_p = array;
int size = sizeof(array)/sizeof(int);
bucketSort(array_p, size);
for(int i = 0; i < size; i++){
printf("%d\n", array[i]);
}
}
3.8暮顺、桶排序(Bucket Sort)
基本思想:根據(jù)元素值特性將待排序集合拆分成多個(gè)區(qū)域厅篓,將這些區(qū)域稱為桶秀存,這些值域(桶)是處于有序的狀態(tài)。對(duì)桶中元素進(jìn)行排序后羽氮,所以元素就處于有序狀態(tài)了或链。
思想交換:
1、快速排序是將待排序集合拆分成兩個(gè)值域(桶)(一個(gè)有序隊(duì)列档押,一個(gè)無(wú)序隊(duì)列澳盐,也可以說(shuō)是兩個(gè)桶),分別對(duì)兩個(gè)值域(桶)進(jìn)行排序令宿,有一點(diǎn)不同的是快排是原地排序叼耙,是對(duì)集合本身排序,而桶排序是多個(gè)值域(桶)粒没,對(duì)每個(gè)值域(桶)排序筛婉,桶排序需要額外的空間,在額外空間對(duì)桶進(jìn)行排序癞松,避免構(gòu)建桶的過(guò)程的元素交換和比較爽撒,同時(shí)可以自主選擇恰當(dāng)?shù)呐判蛩惴ā?br>
2、對(duì)計(jì)數(shù)排序的改進(jìn)响蓉,計(jì)數(shù)排序也需要額外操作空間硕勿,額外空間跨度從最小值到最大值,如果待排序序列不是依次遞增的枫甲,就會(huì)造成操作空間的浪費(fèi)源武。桶排序則弱化操作空間的浪費(fèi),將從最小值到最大值每個(gè)值都申請(qǐng)空間言秸,改進(jìn)成從最小值到最大值按照一定數(shù)值特性分成固定區(qū)域申請(qǐng)空間软能,盡量減少由于元素值不連續(xù)而造成的空間浪費(fèi)迎捺。
桶排序過(guò)程中兩個(gè)關(guān)鍵環(huán)節(jié)【1】:
1举畸、元素值域的劃分,也就是元素到桶的映射規(guī)則凳枝。映射規(guī)則需要根據(jù)待排序集合的元素分布特性進(jìn)行選擇抄沮,若規(guī)則設(shè)計(jì)的過(guò)于模糊、寬泛岖瑰,則可能導(dǎo)致待排序集合中所有元素全部映射到一個(gè)桶上叛买,則桶排序向比較性質(zhì)排序算法演變。若映射規(guī)則設(shè)計(jì)的過(guò)于具體蹋订、嚴(yán)苛率挣,則可能導(dǎo)致待排序集合中每一個(gè)元素值映射到一個(gè)桶上,則桶排序向計(jì)數(shù)排序方式演化露戒。
2椒功、排序算法的選擇捶箱,從待排序集合中元素映射到各個(gè)桶上的過(guò)程,并不存在元素的比較和交換操作动漾,在對(duì)各個(gè)桶中元素進(jìn)行排序時(shí)丁屎,可以自主選擇合適的排序算法,桶排序算法的復(fù)雜度和穩(wěn)定性旱眯,都根據(jù)選擇的排序算法不同而不同晨川。
示例:
待排序集合為:[-7, 51, 3, 121, -3, 32, 21, 43, 4, 25, 56, 77, 16, 22, 87, 56, -10, 68, 99, 70];
映射規(guī)則為:
待續(xù)。删豺。