八大排序算法合集 (歸併排序、交換排序、插入排序、選擇排序......)


內部排序,外部排序

若待排序記錄都在內存中,稱爲內部排序;

若待排序記錄一部分在內存,一部分在外存,則稱爲外部排序。

注:外部排序時,要將數據分批調入內存來排序,中間結果還要及時放入外存,顯然外部排序要複雜得多。

排序算法的穩定性根據相同元素排序前後的順序是否改變來確定

一、歸併排序

遞歸實現 - > 自上向下
非遞歸排序 - > 自下向上
時間複雜度:O(NlogN)O(NlogN)

先分再合

/* 將序列對半拆分直到序列長度爲1*/
void MergeSort_UptoDown(int *num, int start, int end)
{
    int mid = start + (end - start) / 2;
    if (start >= end)
        return;
    MergeSort_UptoDown(num, start, mid);
    MergeSort_UptoDown(num, mid + 1, end);
    Merge(num, start, mid, end);
}

void Merge(int *num, int start, int mid, int end)
{
    int *temp = (int *)malloc((end-start+1) * sizeof(int));    //申請空間來存放兩個有序區歸併後的臨時區域
    int i = start;
    int j = mid + 1;
    int k = 0;
    while (i <= mid && j <= end)
        if (num[i] <= num[j])
            temp[k++] = num[i++];
        else
            temp[k++] = num[j++];
    while (i <= mid)
        temp[k++] = num[i++];
    while (j <= end)
        temp[k++] = num[j++];
    //將臨時區域中排序後的元素,整合到原數組中
    for (i = 0; i < k; i++)
        num[start + i] = temp[i];
    free(temp);
}

二、交換排序

1.快速排序

基本思想:

任取一個元素 (如第一個) 爲中心
所有比它小的元素一律前放,比它大的元素一律後放,形成左右兩個子表;
對各子表重新選擇中心元素並依此規則調整,直到每個子表的元素只剩一個

時間複雜度O(NlogN)O(NlogN)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int n,a[maxn];
void qsorts(int l,int r)
{
    int mid=a[(l+r)/2];
    int i=l,j=r;
    do{
        while(a[i]<mid)i++;
        while(a[j]>mid)j--;
        if(i<=j)
        {
            swap(a[i],a[j]);
            i++;
            j--;
        }
    }while(i<=j);
    if(j>l)qsorts(l,j);
    if(i<r)qsorts(i,r);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    qsorts(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    cout<<endl;
}

2.冒泡排序

O(N2)O(N^2)

void bubble_sort(int a[], int n)  
{
    int i,j,temp;    
    for (j=0;j<n-1;j++)    
    {                           
        for (i=0;i<n-1-j;i++)
        {
            if(a[i]>a[i+1])
            {
                temp=a[i];    
                a[i]=a[i+1];    
                a[i+1]=temp;
            }
        }
    }    
}

三、插入排序

每步將一個待排序的對象,按其關鍵碼大小,插入到前面已經排好序的一組對象的適當位置上,直到對象全部插入爲止。
即邊插入邊排序,保證子序列中隨時都是排好序的

1.直接插入排序(基於順序查找)

void insertSort(int* a,int T){
    int tmp,p;
    for(int i=1;i<T;i++){
        tmp=a[i];
        p=i-1;
        while(p>=0&&tmp<a[p]){
            a[p+1]=a[p];
            p--;
        }
        a[p+1]=tmp;
    }
}

2.折半插入排序(基於折半查找)

(1)基本思想

折半插入排序的基本思想是:順序地把待排序的序列中的各個元素按其關鍵字的大小,通過折半查找插入到已排序的序列的適當位置。

(2)運行過程

直接插入排序的運作如下:

1、將待排序序列的第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。

2、從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置,在查找元素的適當位置時,採用了折半查找方法。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面。)

void binary_insertion_sort(int arr[], int len) 
{
        int i, j, temp, m, low, high;
        for (i = 1; i < len; i++)
        {
               temp = arr[i];
               low = 0; high = i-1;
               while (low <= high)
               {
                      m = (low +high) / 2;
                      if(arr[m] > temp)
                               high = m-1;
                      else
                               low = m+1;
               }
         }
         for (j = i-1; j>=high+1; j--)
               arr[j+1] = arr[j];
         arr[j+1] = temp;
}

3.希爾排序(基於逐趟縮小增量)

#include <stdio.h>
#include <malloc.h>
void shellSort(int *a, int len)
{
    int i, j, k, tmp, gap;  // gap 爲步長
    for (gap = len / 2; gap > 0; gap /= 2) {  // 步長初始化爲數組長度的一半,每次遍歷後步長減半,
    	for (i = 0; i < gap; ++i) { // 變量 i 爲每次分組的第一個元素下標 
	        for (j = i + gap; j < len; j += gap) { //對步長爲gap的元素進行直插排序,當gap爲1時,就是直插排序
	            tmp = a[j];  // 備份a[i]的值
	            k = j - gap;  // j初始化爲i的前一個元素(與i相差gap長度)
	            while (k >= 0 && a[k] > tmp) {
	                a[k + gap] = a[k]; // 將在a[i]前且比tmp的值大的元素向後移動一位
	                k -= gap;
	            }
	            a[k + gap] = tmp; 
	        }
	    }
    }
}
int main(void)
{
    int i, len, * a;
    printf("請輸入要排的數的個數:");
    scanf("%d",&len);
    a = (int *)malloc(len * sizeof(int)); // 動態定義數組
    printf("請輸入要排的數:\n");
    for (i = 0; i < len; i++) { // 數組值的輸入
        scanf("%d",&a[i]);
    }   
    shellSort(a, len); // 調用希爾排序函數
    printf("希爾升序排列後結果爲:\n");
    for (i = 0; i < len; i++) { // 排序後的結果的輸出
        printf("%d\t",a[i]);
    }
    printf("\n");

    return 0;
}



四、選擇排序

0.直接選擇

選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工作原理是每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的數據元素排完。

void Selection_Sort(int Arr[])
	for (int i = 0; i < BUFFSIZE - 1; i++)
		for (int j = i + 1; j < BUFFSIZE; j++)
			if (Arr[i] < Arr[j])  // 將大的元素移到前面
			{
				int tmp = Arr[i];
				Arr[i] = Arr[j];
				Arr[j] = tmp;
			}
    //輸出排序後的元素
	for (int i = 0; i < BUFFSIZE; i++)
		cout << Arr[i] << " ";
	cout << endl;
}

1.堆排序

一個序列,如果將序列看成一個完全二叉樹,非終端結點的值均小於或大於左右子結點的值。

利用樹的結構特徵來描述堆,所以樹只是作爲堆的描述工具,堆實際是存放在線形空間中的。

  • 首先堆是一顆完全二叉樹
  • 其次堆中存儲的值是偏序

Min-heap(小根堆): 父節點的值小於或等於子節點的值

Max-heap(大根堆): 父節點的值大於或等於子節點的值

在這裏插入圖片描述
基本思路
將無序序列建成一個堆
輸出堆頂的最小(大)值
使剩餘的n-1個元素又調整成一個堆,則可得到n個元素的次小值
重複執行,得到一個有序序列

堆的重新調整

輸出堆頂元素後,以堆中最後一個元素替代之
將根結點與左、右子樹根結點比較,並與小者交換
重複直至葉子結點,得到新的堆

時間效率:O(nlogn)O(nlogn)
空間效率:O(1)O(1)
穩 定 性:不穩定
適用於 nn 較大的情況

代碼:

int heap[N],sz=0;
void push(int x)
{
    int i=sz++;
    while(i>0)//往上走
    {
        //父結點的編號
        int p=(i-1)/2;
        //如果不需要再交換就break;
        if(heap[p]<=x)break;
        heap[i]=head[p];
        i=p;
    }
    heap[i]=x;
}
//刪除最小值:先把最小值丟掉,先把最後一個節點的值放到根節點處,然後排序交換即可
int pop()
{
    //最小值
    int ret=heap[0];
    int x=heap[--sz];
    int i=0;
    while(i*2+1<sz)//因爲堆是完全二叉樹偏左嘛
    {
        //左右兒子
        int a=i*2+1,b=i*2+2;
        //選出兒子中最小的
        if(b<sz&&heap[b]<heap[a])a=b;
        //如果不需要交換就break
        if(heap[a]>=x)break;
        //交換
        heap[i]=heap[a];
        i=a;
    }
    heap[i]=x;
    return ret;//返回被丟掉的那個最小值
}


2.二叉堆

二叉堆是一種支持插入、刪除、查詢最值的數據結構,是一棵滿足堆性質的完全二叉樹,樹上的每一個節點都帶有一個權值。

大根堆:
樹上任意一個節點的權值都小於等於其父節點的權值。

小根堆:
樹上任意一個節點的權值都大於等於其父節點的權值。

二叉堆的儲存可以採用層次序列的儲存方式,直接用一個數組保存:按從左到右,從上到下的順序依次爲二叉堆上的節點編號,如果根節點的編號爲1的話,每個節點的左子節點的編號爲根節點編號 2*\ 2,右子節點的編號爲根結點編號 2+1* \ 2 + 1,每個節點的根節點的編號爲自身編號 / 2。

以大根堆爲例討論二叉堆的常見操作:

二叉堆的插入操作:

將新插入的值放在儲存二叉堆的數組的末尾,然後按照二叉堆的規則向上交換,直到滿足二叉堆的性質,時間複雜度爲二叉堆的深度,即:Θ(logN)\Theta(logN)

返回堆頂值:
大根堆堆頂的值爲堆中的最大值,小根堆堆頂的值爲堆中的最小值。

移除堆頂的值:
首先,將堆頂的值與數組末尾的節點交換,之後移除數組末尾的節點(在下面的樣例中,移除節點通過記錄節點個數的n−1n-1n−1來實現);然後,將新的堆頂的值通過交換的方式向下調整,直至滿足二叉堆的性質。

刪除任意一個元素:
與刪除對頂元素類似,將要刪除的元素與數組末尾的元素交換,時候數組長度-1,然後分別檢查是否需要向上或者向下調整,時間複雜度爲Θ(logN)\Theta(logN)

二叉樹的實現可以手寫,也可以使用STL

3.手寫二叉堆代碼

int heap[MAX], n;

void up(int pos) // 向上調整
{
    while (pos > 1)
    {
        if (heap[pos] > heap[pos / 2])
        {
            swap(heap[pos], heap[pos / 2]);
            pos /= 2;
        }
        else
            break;
    }
}

void insert(int val) // 插入節點
{
    heap[++n] = val;
    up(n);
}

int top() // 返回堆頂元素
{
    return heap[1];
}

void down(int pos) // 向下調整
{
    int son = pos * 2;
    while (son <= n)
    {
        if (son < n && heap[son] <= heap[son + 1])
            son++; // 最大的子節點

        if (heap[pos] < heap[son])
        {
            swap(heap[pos], heap[son]);
            pos = son;
            son = pos * 2;
        }
        else
            break;
    }
}

void pop() // 彈出堆頂元素
{
    heap[1] = heap[n];
    n--;
    down(1);
}

void remove(int pos) // 刪除指定位置的元素
{
    heap[pos] = heap[n];
    n--;
    up(pos);
    down(pos);
}


五、排序算法對比

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章