数据结构——排序总结

目录

一、排序的概念

二、排序的分类

三、交换排序(省略冒泡)

1、概念

2、算法思想

3、实现原理

4、快速排序代码

四、归并排序

1、概念

2、代码实现

五、堆排序

1、堆排序基本思想

2、代码实现

六、插入排序

1、概念

2、代码实现

七、基数排序

1、概述

2、代码实现


一、排序的概念

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。(递增或递减)

二、排序的分类

一图以蔽之:

三、交换排序(省略冒泡)

1、概念

快速排序:

又称划分交换排序(partition-exchange sort),简称快排,一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序n个项目要 O(n log2 n)(大O符号)次比较。在最坏状况下则需要 O(n^2)次比较,但这种状况并不常见。事实上,快速排序 (n log n)通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。         

——(维基百科)

快速排序:

分治排序的一种排序算法,它将一个数组分成两个子数组,将两个部分独立的排序。快速排序和归并排序是互补的,归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序,而快速排序则是就当两个子数组都有序时,整个数组就自然有序了。归并中,递归调用发生在处理整个数组之前,而在快速中,递归调用发生在处理整个数组之后

2、算法思想

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

3、实现原理

(1)设置两个变量 low、high,排序开始时:low=0,high=size-1。

(2)整个数组找基准正确位置,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面

        (a)默认数组的第一个数为基准数据,赋值给key,即key=array[low]。

        (b)因为默认数组的第一个数为基准,所以从后面开始向前搜索(high–),找到第一个小于key的array[high],就将 array[high] 赋给 array[low],即 array[low] = array[high]。(循环条件是 array[high] >= key;结束时 array[high] < key)

        (c)此时从前面开始向后搜索(low++),找到第一个大于key的array[low],就将 array[low] 赋给 array[high],即 array[high] = array[low]。(循环条件是 array[low] <= key;结束时 array[low] > key)

        (d)循环 2-3 步骤,直到 low=high,该位置就是基准位置。

        (e)把基准数据赋给当前位置。

(3)第一趟找到的基准位置,作为下一趟的分界点。

(4)递归调用(recursive)分界点前和分界点后的子数组排序,重复2.2、2.3、2.4的步骤。

(5)最终就会得到排序好的数组。

4、快速排序代码

#include<iostream>
using namespace std;

void display(int* array, int size) {
    for (int i = 0; i < size; i++) {
        cout<<array[i];
        if(i!=size-1)
            cout<<" ";
    }
    cout<<endl;
}

int getStandard(int array[], int i, int j) {
    /// 基准数据
    int key = array[i];
    /// i是low,j是high,整个大循环如果i==j就停止
    while (i < j) {
        /// 因为默认基准是从左边开始,所以从右边开始比较
        /// 当队尾的元素大于等于基准数据时,就一直向前挪动 j 指针
        while (i < j && array[j] >= key) {
            j--;
        }
        /// 当找到比 array[i] 小的时,就把后面的值 array[j] 赋给 坑
        if (i < j) {
            array[i] = array[j];
        }
        /// 当队首元素小于等于基准数据时,就一直向后挪动 i 指针
        while (i < j && array[i] <= key) {
            i++;
        }
        /// 当找到比 array[j] 大的时,就把前面的值 array[i] 赋给 新坑
        if (i < j) {
            array[j] = array[i];
        }
    }
    /// 跳出循环时 i 和 j 相等,此时的 i 或 j 就是 key 的正确索引位置
    /// 把基准数据赋给正确位置(基准数据的坑)
    array[i] = key;
    return i;
}

void QuickSort(int array[], int low, int high) {
    /// 开始默认基准为 low
    if (low < high) {
        /// 分段位置下标
        int standard = getStandard(array, low, high);
        /// 递归调用排序
        /// 左边排序
        QuickSort(array, low, standard - 1);
        /// 右边排序
        QuickSort(array, standard + 1, high);
    }
}

/// 合并到一起的快速排序,与上面的是一样的
// void QuickSort(int array[], int low, int high) {
//     if (low < high) {
//         int i   = low;
//         int j   = high;
//         int key = array[i];
//         while (i < j) {
//             while (i < j && array[j] >= key) {
//                 j--;
//             }
//             if (i < j) {
//                 array[i] = array[j];
//             }
//             while (i < j && array[i] <= key) {
//                 i++;
//             }
//             if (i < j) {
//                 array[j] = array[i];
//             }
//         }
//         array[i] = key;
//         QuickSort(array, low, i - 1);
//         QuickSort(array, i + 1, high);
//     }
// }

int main() {
    int array[] = {49, 38, 65, 97, 76, 13, 27, 49, 10};
    int size = sizeof(array) / sizeof(int);

    /// 打印数据
    cout<<size<<" \n";
    QuickSort(array, 0, size - 1);
    display(array, size);

    return 0;
}

四、归并排序

1、概念

归并排序是一种效率较高的排序方法,它分为拆分和合并两大部分,先拆后合。

所谓拆分,就是将一堆无序的数拆成单个,方便合并。

所谓合并,就是将拆分好的数按照排序规则再拼凑起来。

详细解释请看下图:

看完这幅图你应该明白了,归并排序原来就是将一堆数字分开,再合成有序的数列。其实,这就是分治的思想,将大问题化小问题,将每个最小的问题处理好,合并起来大问题也就处理好了。

2、代码实现

#include <iostream>
using namespace std;

///每一趟合并
void merge(int* data,int* tmp,int left,int mid,int right){
    int i=left,j=mid+1;
    int k=left;
    while(i<=mid&&j<=right){
        if(data[i]<data[j])
            tmp[k++]=data[i++];
        else
            tmp[k++]=data[j++];
    }
    while(i<=mid)
        tmp[k++]=data[i++];
    while(j<=right)
        tmp[k++]=data[j++];
    for(int i=left;i<=right;i++)
        data[i]=tmp[i];
}

///拆
void Msort(int* data,int* tmp,int left,int right){
    if(left<right){
        int mid=(left+right)/2;
        Msort(data,tmp,left,mid);
        Msort(data,tmp,mid+1,right);
        merge(data,tmp,left,mid,right);
    }
}

///归并排序入口
void msort(int* data,int n){
    int* tmp=new int[n];
    if(tmp){
        Msort(data,tmp,0,n-1);
        delete[] tmp;
    }
}

int main() {
    int n;
    cin>>n;
    int* data=new int[n];
    for(int i=0;i<n;i++)
        cin>>data[i];
    for(int i=0;i<n;i++)
        cout<<data[i]<<" ";
    cout<<endl;

    ///归并排序入口
    msort(data,n);
    
    for(int i=0;i<n;i++)
        cout<<data[i]<<" ";
    cout<<endl;
    return 0;
}

/**
输入:
10
2 8 4 9 1 10 22 15 11 34
输出:
2 8 4 9 1 10 22 15 11 34
1 2 4 8 9 10 11 15 22 34
*/

五、堆排序

1、堆排序基本思想

(1)将待排序序列构造成一个大顶堆。

(2)此时,整个序列的最大值就是堆顶的根节点。

(3)将其与末尾元素进行交换, 此时末尾就为最大值。

(4)然后将剩余 n-1 个元素重新构造成一个堆, 这样会得到 n 个元素的次小值。 如此反复执行, 便能得到一个有序序列了。

最后可以看到在构建大顶堆的过程中, 元素的个数逐渐减少,最后就得到一个有序序列了。

注:一般升序采用大顶堆, 降序采用小顶堆。

2、代码实现

/**以大顶堆举例*/
#include<iostream>
using namespace std;

void Max_heap(int arr[], int first, int last){
    int par = first;
    int son = par * 2 + 1;

    while(son <= last){
        if(son < last && arr[son] < arr[son + 1]){
            son++;
        }

        if(arr[par] > arr[son]){///如果满足,那么就直接返回
            return;
        }else{
            int temp = arr[par];
            arr[par] = arr[son];
            arr[son] = temp;///交换顺序

            par = son;
            son = par * 2 + 1;///再次进入循环
        }
    }
}

void heap_sort(int arr[], int len){
    for(int i = len / 2 - 1;i >= 0;i--){
        ///从最后一个非叶子节点开始往回走
        Max_heap(arr, i, len-1);
    }///先排好形成大顶堆

    ///下面代码:先将第一个元素和已经排好元素前一位作交换,再进行堆排序
    for(int i = len - 1;i > 0;i--){
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        Max_heap(arr, 0, i - 1);
    }
}

int main(){
    int n;///一共有多少数据
    cin>>n;
    int* arr=new int[n+1];///要排序的数据
    for(int i = 0;i < n;i++){
        cin>>arr[i];
    }

    heap_sort(arr, n);

    for(int i = 0;i < n;i++){
        cout<<arr[i]<<" ";
    }
    cout<<endl;
    delete[] arr;
    return 0;
}

/**
输入:
6
9 2 4 23 10 1
输出:
1 2 4 9 10 23
*/

六、插入排序

1、概念

(1)直接插入排序
把待排序的数据插入到已经排好序的数据中,直到所有的数据插入完成,即就是直接插入排序。(如流行于陕西一带的纸牌游戏,挖坑)一般待排序的区间是第二个数到最后一个数。注意动态移动插入的过程,结合图与代码去理解。

例如下面的例子就是待排序区间从 7 到 8,把第一个数当作已经排序的数据:

 (2)希尔排序

希尔排序实在前面直接插入排序的基础上进行改进的一种排序。直接插入排序的变量 i 其实就是一个间隔,而希尔排序的间隔不是 1,它的间隔逐渐缩小直到为 1 的一种排序(当间隔gap到达1时停止),因此又叫缩小增量法。它是对直接插入排序算法的优化,当间隔不为 1 的时候,都是预排序。下图是举例,第一次的间隔是数据长度的三分之一再加一。即 gap = size / 3 + 1

2、代码实现

#include <iostream>

using namespace std;

///初始化数组
void SetArray(int *data, int size) {
    //srand(time(0));
    cout << "随机初始化" << size << "个数" << endl;
    for (int i = 0; i < size; i++) {
        data[i] = rand() % 100 + 1;
    }
}

///打印函数
void Print(int *data, int size) {
    for (int i = 0; i < size; i++) {
        cout << data[i] << " ";
    }
    cout << endl;
}

///交换函数
void Swap(int &a, int &b) {
    int tmp = a;
    a = b;
    b = tmp;
}

///冒泡排序
void Bubble_Sort(int *data, int size) {
    for (int i = size - 1; i >= 0; i--) {
        bool flag = false;        //、是否交换标志
        for (int j = 0; j < i; j++) {
            if (data[j + 1] < data[j]) {
                Swap(data[j], data[j + 1]);
                flag = true;//发生了交换
            }
        }
        if (flag == false) {//全程无交换
            break;
        }
    }
}

///直接插入排序
void Insertion_Sort(int *data, int size) {
    for (int i = 1; i < size; i++) {
        int tmp = data[i];
        int j;
        for (j = i; j > 0 && data[j - 1] > tmp; j--) {
            ///前一个数大于后一个树,进行交换
            data[j] = data[j - 1];
        }
        ///找到合适位置,进行插入
        data[j] = tmp;
    }
}


///折半插入排序
void Binary_Insertion_Sort(int *data, int size) {
    for (int i = 1; i < size; i++) {
        int low = 0, high = i - 1;//在i之前找到最佳位置
        int tmp = data[i], mid, j;
        while (low <= high) {//寻找最佳位置
            mid = (low + high) / 2;
            if (data[mid] < tmp) {
                //大于中间值,到右半部分寻找
                low = mid + 1;
            } else {//小于中间值,到左半部分寻找
                high = mid - 1;
            }
        }
        for (j = i; j > low; j--) {
            data[j] = data[j - 1];
        }
        data[j] = tmp;
    }
}

///希尔排序(通俗易懂版)
void ShellInsert(int *arr, int len, int gap) {
    int k, i, j, temp;
    ///k循环是处理每一组
    for (k = 0; k < gap; k++) {
        ///i循环是调用了其中一组进行排序
        for (i = k; i < len; i += gap) {
            ///temp是某组第i个数值(都是比较序列的最后一个)
            temp = arr[i];
            ///k永远的每一组的第一个
            for (j = i; j > k; j -= gap) {
                ///条件符合temp与arr[j]交换
                if (temp > arr[j - gap])
                    arr[j] = arr[j - gap];
                else
                    break;
            }
            arr[j] = temp;
        }
    }
}

///调用希尔排序,确定gap(gap大小是循环除以2)
void ShellSort(int *arr, int len) {
    int gap = len / 2;
    while (true) {
        ShellInsert(arr, len, gap);
        Print(arr, len);
        if (gap == 1)
            break;
        gap /= 2;
    }
}

///改进版希尔排序
void Shell_sort(int *arr, int len) {
    int i, j, gap, key;
    ///初始间隔:n/2,每一趟之后除以2
    for (gap = len / 2; gap > 0; gap /= 2) {
        ///每一趟采用插入排序,为什么是i++而不是i+=gap呢,因为是每一组都处理一下
        for (i = gap; i < len; i++) {
            key = arr[i];
            for (j = i; j >= gap && key < arr[j - gap]; j -= gap)
                arr[j] = arr[j - gap];
            ///找到合适位置,进行插入
            arr[j] = key;
        }
        Print(arr, len);
    }
}

int main() {
    cout << "请输入数组长度:" << endl;
    int size, *data;
    cin >> size;
    data = new int[size];

    SetArray(data, size);
    cout << "冒泡排序前:" << endl;
    Print(data, size);
    cout << "冒泡排序后:" << endl;
    Bubble_Sort(data, size);
    Print(data, size);

    SetArray(data, size);
    cout << "直接插入排序前:" << endl;
    Print(data, size);
    cout << "直接插入排序后:" << endl;
    Insertion_Sort(data, size);
    Print(data, size);

    SetArray(data, size);
    cout << "折半插入排序前:" << endl;
    Print(data, size);
    cout << "折半插入排序后:" << endl;
    Binary_Insertion_Sort(data, size);
    Print(data, size);

    SetArray(data, size);
    cout << "希尔排序前:" << endl;
    Print(data, size);
    cout << "希尔排序后:" << endl;
    ShellSort(data, size);
    //Shell_sort(data,size);
    Print(data, size);

    return 0;
}

七、基数排序

1、概述

基数排序的知识点我就不贴出来,相信都能搜到对应概念解释,下面就直接上代码,代码解释其实也很清晰了。

2、代码实现

#include<iostream>
using namespace std;

///补充:(arr[i] / exp) % 10  这个表达式很重要哦,作用是截取到某个数的某一位

/// 获取数组arr中最大值,arr——待排序的数组,len——数组arr的长度。
int arrmax(int *arr, int len) {
    int i, imax;
    imax = arr[0];
    for (i = 1; i < len; i++)
        if (arr[i] > imax)
            imax = arr[i];
    return imax;
}

/// 对数组arr按指数位进行排序。
/// arr——待排序的数组,len——数组arr的长度。
/// exp——排序指数,exp=1:按个位排序;exp=10:按十位排序;......
void _radixsort(int *arr, int len, int exp) {
    int i;
    int result[len];       /// 存放从桶中排序好收集到的数据的临时数组。
    int buckets[10] = {0};   /// 初始化10个桶。

    ///遍历arr,将数据出现的次数存储在buckets中。
    for (i = 0; i < len; i++)
        buckets[(arr[i] / exp) % 10]++;

    ///调整buckets各元素的值,调整后的值就是arr中元素在result中的位置。
    ///核心是下面两行代码,我暂时不知道怎么解释。下面两行代码处理完后buckets的数字
    ///代表的是该桶前面有多少个已经排好序的数
    for (i = 1; i < 10; i++)
        buckets[i] = buckets[i] + buckets[i - 1];

    /// 将arr中的元素填充到result中。注意:一定得是倒序从后面一个一个读取哦!
    ///再说一遍,这里的倒序取非常关键!
    for (i = len - 1; i >= 0; i--) {
        int iexp = (arr[i] / exp) % 10;
        result[buckets[iexp] - 1] = arr[i];
        buckets[iexp]--;
    }

    /// 将排序好的数组result复制到数组arr中。
    ///memcpy(arr, result, len * sizeof(int));
    for (int i = 0; i < len; i++)
        arr[i] = result[i];
}

/// 基数排序主函数,arr——待排序的数组,len——数组arr的长度。
void radixsort(int *arr, int len) {
    int imax = arrmax(arr, len);    /// 获取数组arr中的最大值。

    int iexp;    /// 排序指数,iexp=1:按个位排序;iexp=10:按十位排序;......

    /// 从个位开始,对数组arr按指数位进行排序。
    for (iexp = 1; imax / iexp > 0; iexp = iexp * 10) {
        _radixsort(arr, len, iexp);
        int i;
        ///cout << iexp << " ";     ///输出指数
        for (i = 0; i < len; i++)
            cout << arr[i] << " ";
        cout << endl;
    }
}

int main() {
    ///int arr[] = {144, 203, 738, 905, 347, 215, 836, 26, 527, 602, 946, 504, 219, 750, 848};
    ///int len = sizeof(arr) / sizeof(int);
    int len;
    cin >> len;
    int *data = new int[len];
    for (int i = 0; i < len; i++)
        cin >> data[i];
    for (int i = 0; i < len; i++)
        cout << data[i] << " ";
    cout<<endl;

    radixsort(data, len);  /// 基数排序。

    /// 显示排序结果。
    cout<<"显示最后排完序的结果:"<<endl;
    for (int i = 0; i < len; i++)
        cout << data[i] << " ";
    cout << endl;

    return 0;
}

/**
输入:
15
144 203 738 905 347 215 836 26 527 602 946 504 219 750 848
输出:
144 203 738 905 347 215 836 26 527 602 946 504 219 750 848
750 602 203 144 504 905 215 836 26 946 347 527 738 848 219
602 203 504 905 215 219 26 527 836 738 144 946 347 848 750
26 144 203 215 219 347 504 527 602 738 750 836 848 905 946
显示最后排完序的结果:
26 144 203 215 219 347 504 527 602 738 750 836 848 905 946
*/

我是花花,祝自己也祝您变强了~ 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1.实验目的 掌握内排序,比较各种排序的优、缺点。 2 需求分析 2.1原理 2.1.1、直接排序 算法描述:经过i-1遍处理后,L[1..i-1]己排好序。第i遍处理仅将L[i]插入L[1..i-1]的适当位置,使得L[1..i]又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较L[i]和L[i-1],如果L[i-1]≤ L[i],则L[1..i]已排好序,第i遍处理就结束了;否则交换L[i]与L[i-1]的位置,继续比较L[i-1]和L[i-2],直到找到某一个位置j(1≤j≤i-1),使得L[j] ≤L[j+1]时为止。 2.1.2、冒泡排序 算法描述:核心思想是扫描数据清单,寻找出现乱序的两个相邻的项目。当找到这两个项目后,交换项目的位置然后继续扫描。重复上面的操作直到所有的项目都按顺序排好。 2.1.3、快速排序 算法描述:首先检查数据列表中的数据数,如果小于两个,则直接退出程序。如果有超过两个以上的数据,就选择一个分割点将数据分成两个部分,小于分割点的数据放在一组,其余的放在另一组,然后分别对两组数据排序。通常分割点的数据是随机选取的。这样无论你的数据是否已被排列过,你所分割成的两个字列表的大小是差不多的。而只要两个子列表的大小差不多。 2.1.4、选择排序 算法描述:首先找到数据清单中的最小的数据,然后将这个数据同第一个数据交换位置;接下来找第二小的数据,再将其同第二个数据交换位置,以此类推。 2.1.5、堆排序 (1) 基本思想:堆排序是一树形选择排序,在排序过程中,将R[1..N]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。 (2) 堆的定义: N个元素的序列K1,K2,K3,...,Kn.称为堆,当且仅当该序列满足特性: Ki≤K2i Ki ≤K2i+1(1≤ I≤ [N/2]) 2.1.6、希尔排序 算法描述:在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。 2.2要求 1.本程序对以下六种常用内部排序算法进行实测比较:冒泡排序,插入排序,选择排序,希尔排序,快速排序,堆排序。 2.排序的元素的关键字为整数。用正序,逆序,不同乱序的数据作测试比较。比较的指标为有关键字参加的比较次数和关键字的移动次数。 3.程序以人机对话的形式进行,每次测试完毕显示各种比较指标值 。 2.3任务 设计一个测试程序比较几种内部排序算法的关键字比较次数和移动次数以取得直观感受。 2.4运行环境 (1)WINDOWSXP系统 (2)C++ 编译环境 3.实验方法 本实验主要是内排序,通过比较的次数和移动的次数判断排序的好坏。主要子函数的说明如下。 1.简单选择排序XuanzePaixu(); 2.冒泡排序MaopaoPaixu(); 3. 直接插入排序CharuPaixu(); 4. 快速排序KuaisuPaixu(); 5. 堆排序DuiPaixu(); 6. 希尔排序 XierPaixu(); 以上的排序算法均采用书中所用的算法。程序采用输入的时候仅输入所要的个数,具体的输入数据由程序随机产生个数,并且输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕飞雨的头发不能秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值