今天了整理排序算法的基本實現,主要是復習之前學過的排序算法
聲明:紅色表明的算法為必須掌握
首先是二路歸並排序
/* * 二路歸並排序 * 思路:利用分治思想,對原數組進行二分分段,使元素在每一小段內有序,然后逐漸合並 * 如,最小分段是2,先2內有序,合並為4,4內有序。。。。 * * 時間復雜度:O(nlogn) * 空間復雜度:O(n)需要一個額外的數組作為臨時存儲 */
static void mergeSort(int array[], int helper[], int left, int right) { if (left >= right) return; int mid = (left + right) / 2; mergeSort(array, helper, left, mid); mergeSort(array, helper, mid + 1, right); int helperLeft = left; int helperRight = mid + 1; int cur = left; for (int i = left; i <= right; i++) { helper[i] = array[i]; } while (helperLeft <= mid && helperRight <= right) { if (helper[helperLeft] <= helper[helperRight]) array[cur++] = helper[helperLeft++]; else array[cur++] = helper[helperRight++]; } while (helperLeft <= mid) array[cur++] = helper[helperLeft++]; }
接着是快速排序
/* * 快速排序 * 思路:先選擇一個哨兵元素,然后后往前遍歷,遇到比哨兵小的元素停止,從前往后進行遍歷, * 遇到比哨兵元素大的元素停止,交換兩個元素,繼續,直到前后相遇,此時交換相遇點的元素 * 和哨兵元素,一趟排序下來,可以確保哨兵元素左邊的元素比哨兵小,右邊的元素比哨兵大(升序排序); * 然后分區間再繼續上述過程 * 注意:通常選取左邊界作為哨兵元素,此時,必定先進行從后往前的移動,否則不對稱缺項會出錯 * * 時間復雜度:平均O(nlogn) 最差O(n^2) * 空間復雜度:O(logn)交換時需要一個額外的空間,一共交換logn次 */
static void quickSort(int array[], int left, int right) { if (left >= right) return; int pivot = array[left]; int i = left; int j = right; while (i != j) { while (array[j] >= pivot && j > i) j--; while (array[i] <= pivot && i < j) i++; if (i < j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } } array[left] = array[i]; array[i] = pivot; quickSort(array, left, i - 1); quickSort(array, i + 1, right); }
接着是堆排序
/* * 堆排序 */
//調整堆,larger=true大頂堆,否則為小頂堆
static void adjustHeap(int array[], int parent, int length, bool large) { int pivot = array[parent]; //保存當前父節點
int child = 2 * parent + 1; //獲取左孩子
while (child < length) { if (large) { //大頂堆 //如果有右孩子,且右孩子大於左孩子,選取有孩子節點
if (child + 1 < length && array[child] < array[child + 1]) child++; // 如果父節點的值已經大於孩子節點的值,則直接結束
if (array[parent] >= array[child]) break; } else { //如果有右孩子,且右孩子小於左孩子,選取右孩子節點
if (child + 1 < length && array[child] > array[child + 1]) child++; //如果父節點的值已經小於孩子節點的值,則直接結束
if (array[parent] < array[child]) break; } //把孩子節點的值賦給父節點
array[parent] = array[child]; //選取孩子節點的左孩子節點,繼續向下篩選
parent = child; child = child * 2 + 1;
array[parent] = pivot; } } /* * 初始化堆 * 調整之后,第一個元素為序列的極值 */
static void buildHeap(int array[], int length, bool large) { for (int i = (length - 1) / 2; i >= 0; --i) adjustHeap(array, i, length, large); } /* * 初次建堆時間復雜度O(n) * 調整一次的時間為O(logn),一共調整n次 * 時間復雜度為O(nlogn) * 空間復雜度O(1) */
static void heapSort(int array[], int length, bool increase) { bool large; if (increase) { large = true; } else { large = false; } buildHeap(array, length, large); print(array, length); //將極值逐漸放到隊尾,剩下元素重新調整
for (int i = length - 1; i > 0; i--) { int temp = array[i]; array[i] = array[0]; array[0] = temp; adjustHeap(array, 0, i, large); print(array, length); } }
接着是直接插入排序
/* * 直接插入排序 * 思路:由第二個元素開始,從前向后遍歷,如若當前元素比前一個元素小,則將 * 當前元素設為哨兵元素,保存當前元素,從當前位置向前掃描,尋找哨兵元素應該插入的位置, * 前面的元素依次后移,找到直接插入。然后繼續上述過程 * 時間復雜度:O(n^2) * 空間復雜度:O(1) */
static void directInsertSort(int array[], int length) { //升序版本
for (int i = 1; i < length; i++) { if (array[i] < array[i - 1]) { //找到小數
int j = i - 1; int pivot = array[i]; //復制小數為哨兵元素
array[i] = array[i - 1]; //先進行一次后移
while (pivot < array[j] && j >= 0) { //尋找小數插入位置
array[j + 1] = array[j]; j--; } array[j + 1] = pivot; } } }
接着是二分插入排序
/* * 二分插入排序 * 是直接插入排序的改進,主要體現在尋找哨兵元素插入位置時,使用了二分查找 * 因為哨兵之前的元素一定是已經排序的,所以可以使用二分查找 */
static void binaryInsertSort(int array[], int length) { for (int i = 1; i < length; i++) { if (array[i] < array[i - 1]) { //找到小數
int pivot = array[i]; //復制小數為哨兵元素
int low = 0; int high = i - 1; int mid = 0; while (low <= high) { mid = (low + high) / 2; if (pivot >= array[mid]) { low = mid + 1; } else { high = mid - 1; } } int j = i; //low位置就是要插入的位置,所以low到i之間的元素都需要往后移動一個位置
while (j > low) { //尋找小數插入位置
array[j] = array[j - 1]; j--; } array[low] = pivot; } } }
接着是二路插入排序
/* * 二路插入排序 * 思路:創建一個輔助數組,將此數組當成一個環,環頭存最小元素,環尾存最大元素 * 如果待插入元素比當前最小的元素小,則插入最小元素之前,更新head * 如果待插入元素比當前最大的元素大,則插入最大元素之后,更新tail * 如果在最大最小之間,需要由后向前遍歷,並依次向后移動,尋找插入點,插入 * 最后,將環順序復制並轉化為正常順序 */
static void twoInsertSort(int array[], int length) { //升序版本
int head = 0; //頭指針 小元素
int tail = 0; //尾指針 大元素
int cur = 0; int *helper = new int[length]; helper[0] = array[0]; for (int i = 1; i < length; i++) { if (array[i] < helper[head]) { //待插入的元素比最小的元素小
head = (head - 1 + length) % length; helper[head] = array[i]; } else if (array[i] > helper[tail]) { //待插入的元素比最大的大
tail = (tail + 1 + length) % length; helper[tail] = array[i]; } else { //待插入的元素比最小的大,比最大的小
cur = (tail + 1 + length) % length; //由后向前遍歷,尋找當前元素插入點
while (helper[(cur - 1 + length) % length] > array[i]) { helper[(cur + length) % length] = helper[(cur - 1 + length) % length]; cur = (cur - 1 + length) % length; } //插入元素
helper[(cur + length) % length] = array[i]; tail = (tail + 1 + length) % length; } } for (cur = 0; cur < length; cur++) { array[cur] = helper[(head + cur) % length]; } delete[] helper; }
接着是希爾排序
/* * shell排序(縮小增量排序) * 思路:以一定間隔對數組進行分組,在組上進行直接插入排序,使之有序,之后縮小間隔, * 重復上述過程 * 時間復雜度:O(n^1.5) * 空間復雜度:O(1) */
static void shellSort(int array[], int length) { int gap = length / 2; while (gap >= 1) { //距離間隔gap為一組,遍歷所有組
for (int i = gap; i < length; i++) { if (array[i] < array[i - gap]) { int j = i - gap; int x = array[i]; array[i] = array[j]; //尋找x在當前序列上的插入點
while (x < array[j] && j >= 0) { array[j + gap] = array[j]; j -= gap; } array[j + gap] = x; } } print(array, length); gap /= 2; } }
接着是直接選擇排序
/* * 直接選擇排序 * 思路:(1)從待排序序列中,找到關鍵字最小的元素; * (2)如果最小元素不是待排序序列的第一個元素,將其和第一個元素互換; * (3)從余下的 N - 1 個元素中,找出關鍵字最小的元素,重復(1)、(2)步,直到排序結束。 * 時間復雜度:O(n^2) * 空間復雜度:O(1) */
static void selectSort(int array[], int length) { for (int i = 0; i < length; i++) { for (int j = i + 1; j < length; j++) { if (array[i] > array[j]) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } } } }
改進版的--兩路選擇排序
/* * 兩路選擇排序 * 思路:外循環確定當前元素位置(頭和尾),內循環遍歷剩下元素並記錄最大值和最小值的位置 * 最后交換最值與當前元素 */
static void selectTwoSort(int array[], int length) { for (int i = 0; i <= length / 2; i++) { int min = i, max = i; for (int j = i + 1; j < length - i; j++) { if (array[j] >= array[max]) { max = j; continue; } if (array[j] < array[min]) { min = j; } } int temp = array[i]; array[i] = array[min]; array[min] = temp; temp = array[length - i - 1]; array[length - i - 1] = array[max]; array[max] = temp; } }
接着是冒泡排序
/* * 冒泡排序 * 思路:每次內循環使相鄰元素有序,單次循環后最大值沉底(升序時) * 之后縮短內循環范圍,繼續上述操作,但是 有個問題,太冗雜 * 時間復雜度:O(N^2) * 空間復雜度:O(1) */
static void bubbleSort(int array[], int length) { for (int i = 0; i < length - 1; i++) { for (int j = 0; j < length - i - 1; j++) { if (array[j] > array[j + 1]) { int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } print(array, length); } }
冒泡改進1
/* * 改進冒泡1 * 思路:設置一標志性變量pos,用於記錄每趟排序中最后一次進行交換的位置。 * 由於pos位置之后的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可 */
static void bubbleSort1(int array[], int length) { int i = length - 1; while (i > 0) { int pos = 0; for (int j = 0; j < i; j++) { if (array[j] > array[j + 1]) { pos = j; int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } i = pos; print(array, length); } }
冒泡改進2
/* * 改進冒泡2 * 思路:進行兩個方向的冒泡,以升序為例,正方向上下沉最大值,逆方向上上浮最小值 * 范圍是在不斷縮小的 */
static void bubbleSort2(int array[], int length) { int low = 0; int high = length - 1; int j, temp; int pos = 0; while (low < high) { for (j = low; j < high; j++) { if (array[j] > array[j + 1]) { pos = j; temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } high = pos; for (j = high; j > low; --j) { if (array[j] < array[j - 1]) { pos = j; temp = array[j]; array[j] = array[j - 1]; array[j - 1] = temp; } } low = pos; print(array, length); } }
桶排序
/* * 桶排序 * 思路: */
static void bucketSort(int array[], int length, int max) { int *tempArray = new int[length]; int i; for (i = 0; i < length; i++) tempArray[i] = array[i]; int *count = new int[max]; memset(count, 0, max * sizeof(int)); for (i = 0; i < length; i++) { count[array[i]]++; } for (i = 1; i < max; i++) count[i] = count[i - 1] + count[i]; for (i = length - 1; i >= 0; i--) { array[--count[tempArray[i]]] = tempArray[i]; } }
基數排序
/* * 基數排序 * 思路:由低位到高位,分別用桶排序使之有序 */
//得到最大數的位數
static int getMaxNums(int a[], int n) { int max = ~0; for (int i = 0; i < n; i++) { if (a[i] > max) { max = a[i]; } } int j = 1; while (max >= 10) { j++; max /= 10; } return j; } //得到某位上的數字,最大支持5位
static int getDigit(int x, int d) { int temp[] = { 1, 10, 100, 1000, 10000 }; return ((x / temp[d - 1]) % 10); } //radix always equal 10, as 10 radix
static void radixSort(int a[], int n, int radix) { int *count = new int[radix]; int *bucket = new int[n]; int digits = getMaxNums(a, n); int i = 0, j = 0; //從低位到高位排序
for (int d = 1; d <= digits; d++) { for (i = 0; i < radix; i++) { count[i] = 0; } //統計各個桶要裝入數據的個數
for (i = 0; i < n; i++) { j = getDigit(a[i], d); count[j]++; } // count[i]表示第i個桶的右邊界索引,將桶映射到數組 // 表明當前界限之前有多少個元素要入桶,累加的意圖就是為前面小元素空位 // count[j]-count[j-1]就代表 j桶中元素的個數 //--|---|----|-----|
for (i = 1; i < radix; i++) { count[i] = count[i] + count[i - 1]; } // 將數據依次裝入桶中 // 這里要從右向左掃描,保證排序穩定性
for (i = n - 1; i >= 0; i--) { j = getDigit(a[i], d); bucket[count[j] - 1] = a[i]; //根據索引,得到桶對應數組的位置,從后向前
count[j]--; } for (i = 0; i < n; i++) { a[i] = bucket[i]; } print(a, n); } delete[] count; delete[] bucket; }
外排序 在大數據排序方面用的比較多,目的是減少內存使用,但是io頻率有點高,為了解決這個問題,可以為io添加buff
/* * 外排序 * 思路:首先對大文件分割,並排序存到小文件中。 * 打開所有小文件,每個文件打開時讀入第一個數,數來自哪個文件也要記錄,填滿buf(buf大小和文件個數一致), * 對buf建立最小堆,將堆頂數據寫入輸出文件,並從堆頂元素來自的文件補充一個元素,重新建堆,重復上述。 * 直到寫出的數據個數和總個數相等時,結束 */ #include <stdio.h> #include <time.h> #include <math.h>
//在文件中隨機產生m個數據,每個一行
static void OutSortGenData(char *fileName, int m) { FILE *fp = fopen(fileName, "w"); if (fp == NULL) { printf("open file failed \n"); exit(-1); } srand(time(0)); for (int i = 0; i < m; i++) { int temp = (rand() << 15) | rand(); //rand一般產生15位隨機數(32767),擴展成30位
fprintf(fp, "%d\n", temp); } fclose(fp); } static void OutSortSplit(char *fileName, int m, int n) { FILE *in = fopen(fileName, "r"); int *buf = (int *) malloc(sizeof(int) * n); int k = ceil(double(m) / n); for (int i = 0, j; i < k; i++) { for (j = 0; j < n; j++) { if (fscanf(in, "%d", buf + j) != 1) break; } int num = j; quickSort(buf, 0, num - 1); char tempfile[20]; sprintf(tempfile, "./res/tempfile_%d.txt", i); FILE *out = fopen(tempfile, "w"); for (j = 0; j < num; j++) { fprintf(out, "%d\n", buf[j]); } fclose(out); } free(buf); } typedef struct ospair { int num; int pos; } OPair; static void OutSortAdjust(OPair a[], int parent, int length) { OPair temp = a[parent]; //保存當前父節點
int child = 2 * parent + 1; //獲取左孩子
while (child < length) { /*****小頂堆********/
//如果有有孩子,且右孩子小於做孩子,選取右孩子節點
if (child + 1 < length && a[child].num > a[child + 1].num) { child++; } // 如果父結點的值已經小於孩子結點的值,則直接結束
if (a[parent].num < a[child].num) { break; } // 把孩子結點的值賦給父結點
a[parent] = a[child]; // 選取孩子結點的左孩子結點,繼續向下篩選
parent = child; child = 2 * child + 1; } a[parent] = temp; //PrintHeapSort(a, length);
} /** * 初始堆進行調整 * 將H[0..length-1]建成堆 * 調整完之后第一個元素是序列的最小的元素 */
static void OutSortBuildHeap(OPair a[], int length) { for (int i = (length - 1) / 2; i >= 0; --i) { OutSortAdjust(a, i, length); } } static void OutSortMerge(char *outfile, int m, int n) { FILE *out = fopen(outfile, "w"); int k = ceil((double) m / n); OPair *buf = (OPair *) malloc(sizeof(OPair) * k); FILE **fp = (FILE **) malloc(sizeof(FILE*) * k); for (int i = 0; i < k; i++) { *(fp + i) = (FILE*) malloc(sizeof(FILE)); char tempfile[20]; sprintf(tempfile, "./res/tempfile_%d.txt", i); fp[i] = fopen(tempfile, "r"); int tem; fscanf(fp[i], "%d", &tem); buf[i].num = tem; buf[i].pos = i; } OutSortBuildHeap(buf, k); int nums = 0; while (1) { int minNum = buf[0].num; int minPos = buf[0].pos; if (nums == m) break; fprintf(out, "%d\n", minNum); int tem; fscanf(fp[minPos], "%d", &tem); buf[0].num = tem; OutSortBuildHeap(buf, k); nums++; } for (int i = 0; i < k; i++) { fclose(fp[i]); free(fp[i]); } free(*fp); free(buf); fclose(out); } static void OutSortTest() { char src[] = "./res/data.txt"; char result[] = "./res/out.txt"; int m = 200, n = 20; OutSortGenData(src, m); OutSortSplit(src, m, n); OutSortMerge(result, m, n); }
公共代碼部分
static void print(int array[], int size) { for (int i = 0; i < size; i++) { cout << array[i] << " "; } cout << endl; } static void test() { int array[] = { 21, 40, 300, 101, 3, 2227, 22346, 10000 }; int size = sizeof(array) / sizeof(array[0]); int *helper = new int[size]; // mergeSort(array, helper, 0, size - 1); // quickSort(array, 0, size - 1); // heapSort(array, size, false); // directInsertSort(array, size); // binaryInsertSort(array, size); // twoInsertSort(array, size); // shellSort(array, size); // selectSort(array, size); // selectTwoSort(array, size); // bubbleSort(array, size); // bubbleSort1(array, size); // bubbleSort2(array, size); // bucketSort(array, size, 8);//max 需要大於數組里最大值 // radixSort(array, size, 10);
OutSortTest(); print(array, size); }
附一張網上整理的時間復雜度的對比