目錄
內部排序,外部排序
若待排序記錄都在內存中,稱爲內部排序;
若待排序記錄一部分在內存,一部分在外存,則稱爲外部排序。
注:外部排序時,要將數據分批調入內存來排序,中間結果還要及時放入外存,顯然外部排序要複雜得多。
排序算法的穩定性根據相同元素排序前後的順序是否改變來確定
一、歸併排序
遞歸實現 - > 自上向下
非遞歸排序 - > 自下向上
時間複雜度:
先分再合
/* 將序列對半拆分直到序列長度爲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.快速排序
基本思想:
任取一個元素 (如第一個) 爲中心
所有比它小的元素一律前放,比它大的元素一律後放,形成左右兩個子表;
對各子表重新選擇中心元素並依此規則調整,直到每個子表的元素只剩一個
時間複雜度
#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.冒泡排序
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個元素的次小值
重複執行,得到一個有序序列
堆的重新調整
輸出堆頂元素後,以堆中最後一個元素替代之
將根結點與左、右子樹根結點比較,並與小者交換
重複直至葉子結點,得到新的堆
時間效率:
空間效率:
穩 定 性:不穩定
適用於 較大的情況
代碼:
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。
以大根堆爲例討論二叉堆的常見操作:
二叉堆的插入操作:
將新插入的值放在儲存二叉堆的數組的末尾,然後按照二叉堆的規則向上交換,直到滿足二叉堆的性質,時間複雜度爲二叉堆的深度,即:。
返回堆頂值:
大根堆堆頂的值爲堆中的最大值,小根堆堆頂的值爲堆中的最小值。
移除堆頂的值:
首先,將堆頂的值與數組末尾的節點交換,之後移除數組末尾的節點(在下面的樣例中,移除節點通過記錄節點個數的n−1n-1n−1來實現);然後,將新的堆頂的值通過交換的方式向下調整,直至滿足二叉堆的性質。
刪除任意一個元素:
與刪除對頂元素類似,將要刪除的元素與數組末尾的元素交換,時候數組長度-1,然後分別檢查是否需要向上或者向下調整,時間複雜度爲。
二叉樹的實現可以手寫,也可以使用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);
}