堆排序、归并排序、快速排序

堆排序


堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序是基于堆这种数据结构实现的,什么是堆呢?首先,堆是一棵完全二叉树,其次根据性质不同可以分为以下两种:

  • 大根(顶)堆:每个结点的值均大于等于它的两个孩子结点的值
  • 小根(顶)堆:每个结点的值均小于等于它的两个孩子结点的值

显然,大根堆的堆顶具有整个堆中最大的结点值,小根堆的堆顶具有整个堆中最小的结点值。下面我将基于大根堆(小根堆也是可以的)实现堆排序。
大根堆排序的思想是:

  1. 首先将整个待排序数组(假设长度为n)视为一个完全二叉树,然后将其调整为一个大根堆;
  2. 交换堆顶与堆尾两个元素,然后把堆尾(严格来说此时它已不是堆了,为了方便还这么叫吧)从堆中断开,再把剩余的完全二叉树调整为一个新的大根堆;
  3. 不断重复前面两部直到堆为空,此时排序完成。

很明显堆排序的核心就是如何把一个完全二叉树调整为堆,下面来分析以下:
一棵完全二叉树中,什么样的顶点需要被调整呢?当然是他的孩子节点大于它本身时,显然叶子节点不需要被调整,只有分支结点需要被调整,当我们发现一个需要调整的结点时,非常简单,只需要将它与它结点值较大的那个孩子交换位置即可,每完成一次调整,被换下去的那个结点有可能再次成为一个需要被调整的结点,所以我们需要不断向下检查直到被换下去的结点不满足调整的条件。根据完全二叉树的性质,编号从1~floor(n/2)(floor(a)表示对a向下取整)结点为分支结点。
下面是一个堆排序例子的过程:
堆排序过程

实现代码

public static void heapSort(int[] A){
    //先将原数组调整为一个堆
    buildMaxHeap(A);
    for (int i = A.length;i>1;i--){
        //swap前i表示堆的节点个数,也即堆中最后一个节点的编号,所以其下标为i-1
        //此时堆顶也就是编号为1下标为0的元素为堆中最大的元素,将其交换到堆的末尾
        //此时堆中最大元素处在堆的末尾处,然后将此时编号为1到i-1的元素视为一个新的堆
        //此时从编号i到数组末尾都是已排序好的元素,不断重复此过程,每次都可以在未排序序
        //列(也就是我们的堆)中选出一个最大的元素,最终完成排序

        swap(A,i-1,0);
        新的堆大概率不符合堆的定义,所以需要对其重新调整
        heapAdjust(A,1,i-1);
    }
}
private static void swap(int[] A,int a,int b){
    //交换数组A中下标为a和b的两个元素
    A[a] = A[a]^A[b];
    A[b] = A[a]^A[b];
    A[a] = A[a]^A[b];
}
private static void buildMaxHeap(int[] A){
    for (int i = A.length/2; i > 0; i--){
        //从最后一个分支节点(根据完全二叉树的性质其编号为节点总数/2)逆向开始调整堆
        //i是节点编号不是下标,数组标为为编号-1
        heapAdjust(A,i,A.length);
    }
}
private static void heapAdjust(int[] A,int k,int len){
    /**堆调整算法
     * k:表示被调整的堆的根节点编号,其下标为k-1
     * len:表示待调整的堆的最后一个节点编号
     */
    int root = A[k-1];  //第k个元素的下标为k-1

    for (int i = 2*k; i<=len; i*=2){
        //第k个节点的左孩子编号为2k,其下标为2k-1
        //此时i为左孩子编号其下标为i-1
        if (i + 1 <= len && A[i] > A[i-1]){
            //i+1为有孩子编号,其下标为i
            i = i+1;    //如果有孩子大于左孩子则让i指向有孩子编号
        }
        //此时i一定是两个孩子中较大的那的的编号
        if (root > A[i-1]){
            //如果根节点大于两个孩子节点,则不用再继续调整
            break;
        } else{
            //否则讲较大的孩子节点移动到父节点上
            A[k-1] = A[i-1];
            //修改k值以便继续向下筛选
            //此时
            k = i;
        }
    }
    A[k-1] = root;
}

归并排序


**归并排序(Merge Sort)**是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

"归并"的含义是将两个或两个以上的有序表合成一个新的有序表。假定待排序表含有n个记录,则可将其视为n个有序的子表,每个子表的长度为1,然后两两归并,得到floor(n/2)个长度为2或1的有序表;继续两两归并······,如此重复,直到合并成一个长度为n的有序表为止。下面是动画演示:
归并排序过程

实现代码

public static void mergeSort(int[] A){
    //归并排序,给mSort外面套个壳子,调用起来方便
    //先初始化辅助数组
    Sort.B = new  int[A.length];
    mSort(A,0,A.length-1);

}
private static void mSort(int[] A,int low,int high){
    //归并排序
    if (low < high){
        int mid = (low + high) / 2;
        mSort(A,low,mid);
        mSort(A,mid+1,high);
        merge(A,low,mid,high);
    }
}
private static void merge(int[] A,int low,int mid,int high){
    //首先将A数组中从low到high的元素复制到B数组对应位置
    for (int i = low; i <= high; i++) {
        B[i] = A[i];
    }
    //接下来对两个已经有序子数组进行归并,依次挑选较小的元素放入原数组
    int i,j,k;
    for (i = low,j = mid+1,k = i;i<=mid&&j<=high;k++){
        if (B[i] <= B[j]){
            A[k] = B[i++];
        }else {
            A[k] = B[j++];
        }
    }
    //然后将剩余那个子数组全部放回
    while (i<=mid){
        A[k++] = B[i++];
    }
    while (j<=high){
        A[k++] = B[j++];
    }
}

快速排序


快速排序(Quick Sort) 是由冒泡排序改进而得的。在冒泡排序过程中,只对相邻的两个记录进行比较,因此每次交换两个相邻记录时只能消除一个逆序。如果能通过两个(不相邻)记录的一次交换直接消除多个逆序,则会大大加快排序的虚度。快速排序方法中的一次交换可以消除多个逆序。

算法步骤

在待排序的n个记录中任取一个记录(通常选取第一个记录)作为枢轴,设其关键字为pivotkey。经过一趟排序后,把所有关键字小于pivotkey的记录交换的前面,把所有大于pivotkey的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后对左右两个子表重复上述过程,直到每一个子表只有一个记录时,排序完成。换句话说,就是每一趟排序都将一条记录放在了正确的位置上。(正确位置就是完成排序时它所处的位置)

具体步骤

  1. 选择待排序表中第一个记录作为枢轴,保存在变量pivotkey中。附设两个指针lowhigh,初始时分别指向表的上界和下界(第一趟时,low = 0;high = nums.length)。
  2. 从表的最右侧位置依次向左侧搜索,当找到第一个值小于pivotkey的记录时,将其移动到low处。具体操作是:当low < high时,若high所指记录大于等于pivotkey,则向左移动指针high;否则移动该记录。
  3. 然后从表的最左侧位置依次向右侧搜索,找到第一个关键字大于pivotkey的记录,将其移动到high处。具体操作是:若low所指记录的值小于pivotkey,则向右移动指针low;否则移动该记录。
  4. 重复步骤2和3,直至lowhigh。此时lowhigh的位置即为枢轴在此躺排序的最终位置,原表被分为两个子表。

下面是快速排序的动画演示:
快速排序过程

代码实现

public class Sort {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 321, 34, 54, 56, 745, 7546,3,423,463,64,234,143421};
        quickSort(nums);
        for (int n : nums) {
            System.out.println(n);
        }
//        1
//        3
//        34
//        54
//        56
//        64
//        234
//        321
//        423
//        463
//        745
//        7546
//        143421
    }
    public static void quickSort(int[] nums) {
        qSort(nums,0,nums.length - 1);
    }
    public static void qSort(int[] nums,int low,int high) {
        if(low < high) {
            int pivotloc = partition(nums,low,high);
            qSort(nums,low,pivotloc - 1);
            qSort(nums,pivotloc+1,high);
        }

    }
    public static int partition(int[] nums,int low,int high) {
        int pivotkey = nums[low];  //用于比较的枢轴
        while (low < high) {
            while (low < high && nums[high] >= pivotkey) { high --; }
            nums[low] = nums[high];
            while (low < high && nums[low] <= pivotkey) { low ++;}
            nums[high] = nums[low];
        }
        nums[low] = pivotkey;
        return low;
    }
}

如有错误恳请指正

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您解答这个问题。 首先,堆排序快速排序归并排序和计数排序都是常见的排序算法,它们的实现方式各不相同。下面分别介绍它们的C++实现。 1. 堆排序(Heap Sort) 堆排序是一种选择排序,它利用堆的性质进行排序。堆是一个完全二叉树,其中每个节点的值都大于或等于其子节点的值(最大堆),或者小于或等于其子节点的值(最小堆)。在堆排序中,我们将待排序数组构建成一个最大堆,然后将堆顶元素(即最大值)与数组最后一个元素交换,然后重新调整堆,重复执行此操作,直到整个数组有序。 下面是堆排序的C++实现: ```c++ void heapify(int arr[], int n, int i) { int largest = i; // 初始化最大值为根节点 int l = 2 * i + 1; // 左子节点索引 int r = 2 * i + 2; // 右子节点索引 // 如果左子节点比最大值大,则更新最大值索引 if (l < n && arr[l] > arr[largest]) largest = l; // 如果右子节点比最大值大,则更新最大值索引 if (r < n && arr[r] > arr[largest]) largest = r; // 如果最大值不是根节点,则交换根节点和最大值,然后递归调整堆 if (largest != i) { swap(arr[i], arr[largest]); heapify(arr, n, largest); } } void heapSort(int arr[], int n) { // 构建最大堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 依次取出堆顶元素,放到数组末尾,并重新调整堆 for (int i = n - 1; i >= 0; i--) { swap(arr[0], arr[i]); heapify(arr, i, 0); } } ``` 2. 快速排序(Quick Sort) 快速排序是一种分治排序,它利用分治的思想将大问题分解成小问题,然后递归地解决小问题。在快速排序中,我们选择一个枢轴元素(一般选择第一个元素),将数组分为两个部分,左边部分的元素均小于枢轴元素,右边部分的元素均大于枢轴元素。然后分别对左右两部分递归执行此操作,直到整个数组有序。 下面是快速排序的C++实现: ```c++ int partition(int arr[], int low, int high) { int pivot = arr[low]; // 枢轴元素 int i = low, j = high; while (i < j) { // 从右往左找第一个小于枢轴元素的位置 while (i < j && arr[j] >= pivot) j--; // 从左往右找第一个大于枢轴元素的位置 while (i < j && arr[i] <= pivot) i++; // 交换左右两个元素 if (i < j) swap(arr[i], arr[j]); } // 将枢轴元素放到正确的位置上 arr[low] = arr[i]; arr[i] = pivot; return i; } void quickSort(int arr[], int low, int high) { if (low < high) { int pivot = partition(arr, low, high); quickSort(arr, low, pivot - 1); quickSort(arr, pivot + 1, high); } } ``` 3. 归并排序(Merge Sort) 归并排序也是一种分治排序,它将待排序数组分成两部分,分别排序,然后合并成一个有序数组。在归并排序中,我们先将数组分为两半,分别对左半部分和右半部分递归执行归并排序,然后将两个有序子数组合并成一个有序数组。 下面是归并排序的C++实现: ```c++ void merge(int arr[], int l, int m, int r) { int n1 = m - l + 1; int n2 = r - m; // 创建临时数组 int L[n1], R[n2]; // 将左半部分复制到临时数组 L for (int i = 0; i < n1; i++) L[i] = arr[l + i]; // 将右半部分复制到临时数组 R for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i]; // 合并左右两个有序子数组 int i = 0, j = 0, k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 将剩余的元素复制到数组中 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int arr[], int l, int r) { if (l < r) { int m = l + (r - l) / 2; mergeSort(arr, l, m); mergeSort(arr, m + 1, r); merge(arr, l, m, r); } } ``` 4. 计数排序(Counting Sort) 计数排序是一种非比较排序,它利用元素出现的次数来确定每个元素在有序数组中的位置。在计数排序中,我们先统计每个元素出现的次数,然后计算每个元素在有序数组中的位置,最后将元素放到正确的位置上。 下面是计数排序的C++实现: ```c++ void countingSort(int arr[], int n) { int max_val = 0; for (int i = 0; i < n; i++) { if (arr[i] > max_val) max_val = arr[i]; } // 创建计数数组 int count[max_val + 1] = {0}; // 统计每个元素出现的次数 for (int i = 0; i < n; i++) count[arr[i]]++; // 计算每个元素在有序数组中的位置 for (int i = 1; i <= max_val; i++) count[i] += count[i - 1]; // 将元素放到正确的位置上 int output[n]; for (int i = n - 1; i >= 0; i--) { output[count[arr[i]] - 1] = arr[i]; count[arr[i]]--; } // 将有序数组复制到原数组中 for (int i = 0; i < n; i++) arr[i] = output[i]; } ``` 以上就是堆排序快速排序归并排序和计数排序的C++实现,希望能对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值