【算法】之常见的排序算法

  我们平时说的“排序”,指的是内部排序,即使用内存资源进行排序的。除了内部排序之外,还有外部排序。本文主要介绍内部排序。

  内部排序分为插入排序、选择排序、交换排序、归并排序等。其中,插入排序又分为直接插入排序和希尔排序;选择排序分为简单选择排序和堆排序;交换排序又分为冒泡排序和快速排序。

  也就是说,本文中主要介绍其中排序方法,分别是:直接插入排序、希尔排序、简单选择排序、堆排序、冒泡排序、快速排序、归并排序。

1、直接插入排序

  直接插入排序(Straight Insertion Sort)的基本思想是:将一个记录插入到已排序好的有序表中,从而得到一个新的、记录数增1的有序表。即:先将序列的第1个元素看成是一个有序的子序列,然后从第2个元素开始逐个进行插入,直至整个序列有序为止。直接插入排序的核心代码如下:

#include <cstdio>

// 直接插入排序的核心函数
void performSort(int array[], int n) {
    for (int i = 1; i < n; i++) {
        int tmp = array[i];
        int j = i - 1;
        for (; j >= 0; j--) {
            if (array[j] > tmp) {
                array[j + 1] = array[j];
            } else {
                break;
            }
        }
        array[j + 1] = tmp;
    }
}

2、希尔排序

  希尔排序(Shell‘s Sort)的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。举例说明:

例如,对于一个整形数组:49 38 65 97 76 13 27 71 76 34
数组长度为10,我们可以把最初的增量设置为数组总长度的一半:5
即:第一遍做直接插入排序的元素有:49和13、38和27、65和71、97和76、76和34。
这样,经过第一遍的排序,得到的数组结果为:13 27 65 76 34 49 38 71 97 76
第二遍,我们将增量减少为上一边的一半:5 / 2 = 2
同上,经过第二遍的排序,得到的数组结果为:13 27 34 49 38 71 65 76 97 76
第三遍,我们再讲增量减少为上一遍的一半:2 / 2 = 1
此时的增量是1,无法再减少增量,因此,我们只需要再进行一次直接插入排序,就可以得到最终的排序结果了
由于经过前两次的增量排序,已经把大致的结果排了出来,因此,最后一次的排序需要交换的元素就很少了

  希尔排序的核心代码如下:

#include <cstdio>

// 希尔排序的核心函数
void performSort(int array[], int n) {
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            for (int j = i - gap; j >= 0 && array[j] > array[j + gap]; j -= gap) {
                int temp = array[j];
                array[j] = array[j + gap];
                array[j + gap] = temp;
            }
        }
    }
}

3、简单选择排序

  简单选择排序(Simple Selection Sort)的基本思想是:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。可以发现,在简单选择排序中,一趟只能确定一个元素(即最小的元素)。我们可以对简单选择排序进行改进,使得在一趟中可以确定两个元素(即最大的元素和最小的元素),这样可以有效地减少循环所需的资源,提高效率。简单选择排序的核心代码如下:

#include <cstdio>

// 简单选择排序
void performSort(int array[], int n) {
    int i, j, min, max, tmp;
    for (i = 1; i <= n / 2; i++) {
        min = i;
        max = i;
        for (j = i + 1; j <= n - i; j++) {
            if (array[j] > array[max]) {
                max = j;
                continue;
            }
            if (array[j] < array[min]) {
                min = j;
            }
        }
        tmp = array[i - 1];
        array[i - 1] = array[min];
        array[min] = tmp;
        tmp = array[n - i];
        array[n - i] = array[max];
        array[max] = tmp;
    }
}

4、堆排序

  在了解堆排序之前,先了解一下关于堆(二叉堆)的知识:

(1)二叉堆的特性:
父节点的值总是大于或等于(小于或等于)任何一个子节点的值(父节点大于或等于任何一个子节点的堆称为最大堆,反之是最小堆);
每个节点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
(2)二叉堆的存储结构:
二叉堆一般用数组来表示。下标为i的节点的父节点的下标是(i-1)/2;它的左子节点的下标为i*2+1;它的右子节点的下标为i*2+2。
(3)通过数组建立二叉堆:
先将数组转化为一棵完全二叉树,然后再根据最大堆或最小堆的特性,交换树中的某些元素,最终就可以形成一个二叉堆。
(4)向二叉堆中插入元素:
将新插入的元素添加到数组的末尾(即树的最后一个叶子节点),然后再根据最大堆或最小堆的特性,交换树中的某些元素,恢复二叉堆的次序。
(5)从二叉堆中删除元素:
这里的删除总是删除树根元素,此时要将堆中的最后一个元素填补树根的空缺,然后根据特性交换元素,恢复二叉堆的次序。

  堆排序的基本思想是:利用了堆(二叉堆)这种数据结构的特性,将排序操作的时间复杂度从n^2降为了n*logn。开始的时候,创建一个堆,找到堆顶元素(即数组中最大的元素),与数组中的最后一个元素交换位置。以后每个找到的最大的数都放到前一次找到的数的前面,这样,就形成了一个从后往前的递减数组,从而实现了递增排序。堆排序的核心代码如下:

#include <cstdio>

// 对堆进行调整
void adjustHeap(int array[], int i, int n) {
    int j = 2 * i + 1, temp = array[i];
    while (j < n) {
        if (j + 1 < n && array[j + 1] > array[j]) j++; //在左右孩子中找最小的
        if (array[j] <= temp) break;
        array[i] = array[j]; // 把较小的子结点往上移动,替换它的父结点
        i = j;
        j = 2 * i + 1;
    }
    array[i] = temp;
}

// 将数组调整成堆,调整完毕后第一个元素是数组中最小的元素
void buildHeap(int array[], int n) {
    for (int i = n / 2 - 1; i >= 0; i--)
        adjustHeap(array, i, n);
}

// 堆排序的核心函数
void performSort(int array[], int n) {
    buildHeap(array, n);
    for (int i = n - 1; i > 0; --i) {
        int temp = array[i];
        array[i] = array[0];
        array[0] = temp;
        adjustHeap(array, 0, i);
    }
}

5、冒泡排序

  传统冒泡排序的基本思想是: 在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。这种方法每一趟排序操作只能找到一个最大值或最小值。我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法,一次可以得到两个最终值(最大者和最小者),从而使排序趟数几乎减少了一半。改进后的冒泡排序的核心代码如下:

#include <cstdio>

// 冒泡排序的核心函数
void performSort(int array[], int n) {
    int low = 0;
    int high = n - 1; // 设置变量的初始值
    int tmp, j;
    while (low < high) {
        for (j = low; j < high; ++j) // 正向冒泡,找到最大者
            if (array[j] > array[j + 1]) {
                tmp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = tmp;
            }
        --high; // 修改high值, 前移一位
        for (j = high; j > low; --j) // 反向冒泡,找到最小者
            if (array[j] < array[j - 1]) {
                tmp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = tmp;
            }
        ++low; // 修改low值,后移一位
    }
}

6、快速排序

  快速排序的基本思想如下:

1)选择一个基准元素,通常选择第一个元素或者最后一个元素;
2)通过一趟排序将待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小,另一部分记录的元素值比基准值大;
3)此时基准元素在其排好序后的正确位置;
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

  快速排序的核心代码如下:

#include <cstdio>

void swap(int &a, int &b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int partition(int array[], int low, int high) {
    int privotKey = array[low]; // 基准元素
    while (low < high) { // 从表的两端交替地向中间扫描
        while (low < high && array[high] >= privotKey) --high; // 从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
        swap(array[low], array[high]);
        while (low < high && array[low] <= privotKey) ++low;
        swap(array[low], array[high]);
    }
    return low;
}

void quickSort(int array[], int low, int high) {
    if (low < high) {
        int privotLoc = partition(array, low, high); // 将表一分为二
        quickSort(array, low, privotLoc - 1); // 递归对低子表递归排序
        quickSort(array, privotLoc + 1, high); // 递归对高子表递归排序
    }
}

// 快速排序的核心函数
void performSort(int array[], int n) {
    quickSort(array, 0, n - 1);
}

7、归并排序

  归并排序的基本思想是:将两个(或多个)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,对每个子序列进行排序使其有序,最后再把有序子序列合并为整体有序的序列。归并排序的核心代码如下:

#include <cstdio>

// 将一个数组的两个有序数列a[first...mid]和a[mid+1...last]合并
void mergeArray(int array[], int temp[], int first, int mid, int last) {
    int i = first, j = mid + 1;
    int m = mid, n = last;
    int k = 0;
    while (i <= m && j <= n) {
        if (array[i] <= array[j]) temp[k++] = array[i++];
        else temp[k++] = array[j++];
    }
    while (i <= m) temp[k++] = array[i++];
    while (j <= n) temp[k++] = array[j++];
    for (i = 0; i < k; i++) array[first + i] = temp[i];
}

void mergeSort(int array[], int temp[], int first, int last) {
    if (first < last) {
        int mid = (first + last) / 2;
        mergeSort(array, temp, first, mid);
        mergeSort(array, temp, mid + 1, last);
        mergeArray(array, temp, first, mid, last);
    }
}

// 归并排序的核心函数
void performSort(int array[], int n) {
    int temp[n];
    mergeSort(array, temp, 0, n - 1);
}

七大排序方法总结

1、各种排序方法的时间复杂度和稳定性:

(注:若待排序的序列中,存在多个具有相同关键字的记录,经过排序,这些记录的相对次序保持不变,则称该算法是稳定的。)

2、不同情况下各种排序算法的选择原则:

(1)当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);
(2)当数据量较大时,应采用时间复杂度为O(n*log2^n)的排序方法:快速排序、堆排序或归并排序;
(3)当数据量较小时,可采用直接插入或直接选择排序;
(4)一般不使用或不直接使用传统的冒泡排序;
(5)当数据量较大、内存空间允许、且要求稳定性时,可以选择使用归并排序。
时间: 2024-10-25 13:42:35

【算法】之常见的排序算法的相关文章

算法之常见的排序算法

一.冒泡排序 @run_time def bubble_sort(data_list): """ 冒泡排序: 首先拿到第一个元素,和它第二个作比较,看是否交换位置 第二个再和第三个比较....一轮下来,最后一个就是最大的数 冒泡都是临近的互相对比找到最大的数据 :param data_list: :return: """ # [6,3,2,7,8,9,5,1,4] # [] for i in range(len(data_list)): flag

java几种常见的排序算法总结

[java] view plain copy /*************几种常见的排序算法总结***************************/ package paixu; public class PaiXu { final int MAX=20; int num[]=new int[MAX]; { System.out.print("生成的随机数组是:"); for(int i=0;i<20;i++){ num[i]=(int)(Math.random()*100)

数据结构之常见的排序算法c语言实现

常见的简单排序算法有冒泡排序.选择排序.插入排序.快排.堆排序.归并排序.希尔排序等,这些排序的理论在网上有很多,这就只给出常见的排序算法源码,上学时候写的,不足之处欢迎大家指正. 下面几种排序的主函数入口为:     int main(int argc, char* argv[])         {      int i, len;      int a[] = {8,5,6,4,9,10,3,15,2,17};           len = (sizeof(a) / sizeof(a[0

常见比较排序算法的比较

几种常见的排序算法之比较 排序的基本概念以及其算法的种类,介绍几种常见的排序算法的算法:冒泡排序.选择排序.插入排序.归并排序.快速排序.希尔排序的算法和分析它们各自的复杂度,然后以表格的形式,清晰直观的表现出它们的复杂度的不同.在研究学习了之前几种排序算法的基础上,讨论发现一种新的排序算法,并通过了进一步的探索,找到了新的排序算法较之前几种算法的优势与不足. 排序算法,是计算机编程中的一个常见问题.在日常的数据处理中,面对纷繁的数据,我们也许有成百上千种要求,因此只有当数据经过恰当的排序后,才

常见的排序算法

描述: 排序算法可谓数据结构模块中的重中之重,常见的哈希表,二叉树,搜索树/平衡树,位图等数据结构只是处理实际问题的抽象方法,实际在处理接受或生成的数据集时,排序算法显得尤其重要,排序算法家族很庞大,其中包括了冒泡排序,选择排序,插入排序,堆排序,快速排序,归并排序,基数排序,计数排序,希尔排序,箱排序,树型排序等众多算法,每种排序都有各自的特性,没有好坏之分,只有在特定的场景使用合适的排序算法才是上策,单纯的来比显得太过绝对,没有可比性.因为实际需求及各方面条件的限制使得排序算法的可选范围往往

Python全栈开发之5、几种常见的排序算法以及collections模块提供的数据结构

在面试中,经常会遇到一些考排序算法的题,在这里,我就简单了列举了几种最常见的排序算法供大家学习,说不定以后哪天面试正好用上,文章后半段则介绍一下collections模块,因为这个模块相对于python提供的基本数据结构(list,tuple,dict)不被人们所熟悉,但是如果你对他们了解的话,用起来也是非常方便高效的. 排序算法 一.冒泡排序(BubbleSort) 步骤: 比较相邻的元素,如果第一个比第二个大,就交换他们两个. 循环一遍后,最大的数就“浮”到了列表最后的位置. 将剩下的数再次

用Java来写常见的排序算法

随着校招的临近 算法是校招中很重要的一个部分 总结了常见几种排序算法,各种算法的时间复杂度和空间复杂度大家也需要多了解下 package com.huwei.sort; /** * 各种排序算法 * * @author huwei * */ public class Sort { public static void main(String[] args) { int[] a = { 60, 57, 89, 47, 57, 98, 45, 35, 73 }; Sort sort = new So

十种常见的排序算法,面试算法必考

1.冒泡排序 已知一组无序数据a[1].a[2].……a[n],需将其按升序排列.首先比较a[1]与a[2]的值,若a[1]大于a[2]则交换两者的值,否则不变.再比较a[2]与a[3]的值,若a[2]大于a[3]则交换两者的值,否则不变.再比较a[3]与a[4],以此类推,最后比较a[n-1]与a[n]的值.这样处理一轮后,a[n]的值一定是这组数据中最大的.再对a[1]~a[n-1]以相同方法处理一轮,则a[n-1]的值一定是a[1]~a[n-1]中最大的.再对a[1]~a[n-2]以相同方

常见的排序算法--java版

个人总结的常见的排序算法 public class Sort { // 1.冒泡:稳定,最优O(n) 最差O(n^2) 平均O(n^2) private static void sort1(int[] arr) { for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr.length - 1; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = ar

几种常见的排序算法

1.插入类排序 在一个已经有序的序列中,插入一个新的记录.有直接插入排序.折半插入排序.希尔排序. 插入类排序 直接插入排序 1 void InsertSort(int R[], int n) 2 { 3 int i, j; 4 int temp; 5 for (i = 1; i < n; ++i) 6 { 7 temp = R[i]; 8 j = i - 1; 9 while (j >= 0 && temp < R[j]) 10 { 11 R[j+1] = R[j];