常用数据结构之排序算法总结


今天了整理排序算法的基本实现,主要是复习之前学过的排序算法

声明:红色表明的算法为必须掌握

首先是二路归并排序

/* * 二路归并排序 * 思路:利用分治思想,对原数组进行二分分段,使元素在每一小段内有序,然后逐渐合并 * 如,最小分段是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); }

 附一张网上整理的时间复杂度的对比

 

文件


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM