【数据结构C/C++】十大排序算法的实现思路以及易写易记忆版代码实现

news2024/11/30 0:32:04

文章目录

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 归并排序
  • 快速排序(重点讲解)
  • 堆排序(重点理解)
  • 408考研各数据结构C/C++代码(Continually updating)

冒泡排序

时间复杂度 O(n2)
空间复杂度 O(1)
冒泡排序的思想是,从第一个开始遍历,然后每次都把比较大的数据
移动到后面去,那么在第一次交换的时候,最大的数据已经到最后去了,
以此类推,第二次冒泡的话,倒数第二个就是倒数第二大的数据。

#include <stdio.h>

void bubbleSort(int arr[], int n)
{
    if (arr == NULL || n < 2)
    {
        return;
    }
    int flag = 0;
    // 这是第几轮
    for (int i = 0; i < n; i++)
    {
        flag = 0;
        // 这一层的for循环的意思就是当前轮需要包含的数据的个数
        // 因为每一次冒泡,都会有一个最大的数据到末尾去
        // 所以需要不断的减少需要参与计算的数据量
        for (int j = 0; j < n - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                // 使用异或交换
                arr[j] = arr[j] ^ arr[j + 1];
                arr[j + 1] = arr[j] ^ arr[j + 1];
                arr[j] = arr[j] ^ arr[j + 1];
                flag = 1;
            }
        }
        //如果此时已经没有数据更新,那么说明已经有序,直接跳出循环
        if (flag == 0)
        {
            break;
        }
    }
}

int main()
{
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("原始数组:\n");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    bubbleSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

选择排序

时间复杂度 O(n^2)
空间复杂度 O(1)
思路是从第一个开始遍历,然后每次都选择最小的哪一个放到前面去。
分区间: 将数组分为两部分,一部分是已排序的,另一部分是未排序的。初始时,整个数组都被视为未排序。
找最小值: 在未排序的部分中,查找最小的元素。通常,这是通过遍历未排序部分的所有元素并不断更新一个临时最小值和其索引来实现。
交换位置: 一旦找到最小元素,将其与未排序部分的第一个元素交换位置,使其成为已排序部分的一部分。
增加已排序部分的大小: 将已排序部分的大小递增,同时减少未排序部分的大小。
重复: 重复步骤2到步骤4,直到整个数组都被排序。未排序部分会不断减小,而已排序部分会逐渐增加。
完成: 当未排序部分为空时,排序过程完成,整个数组被排序。
上面是我早期写的笔记,比较书面。口语的意思其实就是,在开始排序之前,先记录下一次要插入的最小的数据的位置,一开始其实就是0,然后记录下来这个起始位置p,并且设定这个起始位置的值为暂定的min最小值,然后从起始位置开始遍历整个数组,找到最小的那个数据,并且记录下来这个位置的索引,并且更新我们暂定的min的值为这个遍历到的更小的值。
之后,我们遍历完毕一次数组之后,我们就得到了从起始位置到达数组末尾中的那个最小值的索引和最小值。
此时,我们进行交换,将这个最小值交换到我们一开始设定的其实位置p,当然,我们肯定不能直接就覆盖了这个起始位置的值,还需要将我们一开始设定的起始位置的值覆盖掉这个我们后来找到的最小值的索引,不然这个最小值就会一直参与计算了。

#include <stdio.h>

void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = arr[i];  // 初始化最小值为当前元素
        int p = i;  // 用于记录最小值的索引

        // 在未排序部分查找最小值
        for (int j = i + 1; j < n; j++) {
            if (min > arr[j]) {
                min = arr[j];
                p = j;
            }
        }

        // 如果找到更小的值,交换它们
        if (p != i) {
            int temp = arr[p];
            arr[p] = arr[i];
            arr[i] = temp;
        }
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("原始数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    selectionSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

插入排序

时间复杂度 O(n2)
空间复杂度 O(1)
插入排序的概念是:每次选取一个索引,从0开始。然后判断当前数据是不是比前面一个小,
如果是,那么就开始交换,并且一直交换,直到比前面的大,或者前面没有数据了为止。
因此插入在高度有序的情况下,时间复杂度O(n),最差O(n2)
插入排序一般性能都比冒泡和选择排序好 因为他会在数据有序的时候直接结束内循环。
这里比较重点的就是,插入排序每次遍历的时候,是向前遍历,也就是每次都要确保当前元素是大于前面元素的,如果不成立,也就是arr[j]>arr[j+1],那么插入排序就会开始向前交换元素。直到不成立或者遇到索引0.
我甚至认为插入排序最简单,因为他实际有效的代码就两行!!!

#include <stdio.h>

void insertSort(int arr[], int n) {
	//从第一个元素开始遍历,然后内循环比较当前元素的前面一个元素和当前元素的关系
    for (int i = 1; i < n; i++) {
        // 这里的条件判断就是 j 前面还有数据,并且前面的 j 比后面的数据大
        // 那么就需要进行一次交换
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            // 使用异或交换
            arr[j] = arr[j] ^ arr[j + 1];
            arr[j + 1] = arr[j] ^ arr[j + 1];
            arr[j] = arr[j] ^ arr[j + 1];
        }
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("原始数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    insertSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

归并排序

时间复杂度 O(nlogn)
空间复杂度 O(n)
归并排序的思路:
使用递归的方式不断将数组进行拆分,拆分为左右两个等大小的数组。
由于递归的特性,最后会拆分为长度最小为2的一个小区间,之后我们就对这个小区间进行排序即可。
最后,我们就可以得到多个的有序的小区间。
然后递归返回之后,又会继续对这些小区间合并后的一个区间进行合并,然后不断递归返回,
就可以得到最终有序的一个区间了。
而递归调用的一个重要思路就是,使用一个临时数组,这个临时数组用于存放左右区间里的数据,并且会按照大小进行排序。
思路为,使用p1(左指针),使用p2(右指针)的方式,左指针指向left,右指针指向mid+1,
然后左右指针不断后移,直到越界,左指针边界为left—mid,右指针边界为mid+1—right。
然后将左右指针中指向的更小的数据拷贝到临时数组中,此时临时数组中的元素在区间内有序。
之后将这个区间内有序的数据,覆盖原有的数组即可。
之所以归并排序可以把时间复杂度缩短到nlogn,是因为相对于时间复杂度n2的排序,这些排序浪费了大量的比较功能
而归并排序每次都会比较两个范围的数据,并且将其合并成一个区间内有序的数据,然后再将这个区间去和更大的区间进行
一次排序,那么这样子,他的比较的操作就没有浪费,而是保留了下来。
代码如下:

#include <stdio.h>

// 提前声明merge函数
void merge(int arr[], int left, int middle, int right);
void mergeSort(int arr[], int left, int right);

void mergeSort(int arr[], int left, int right)
{
    if (left == right)
    {
        return;
    }
    //划分中轴
    int mid = ((right - left) >> 1) + left;
    //对左边进行归并
    mergeSort(arr, left, mid);
    //对右边进行归并
    mergeSort(arr, mid + 1, right);
    //排序
    merge(arr, left, mid, right);
}
//对left到right这个区间上的数据进行排序
void merge(int arr[], int left, int mid, int right)
{
    // 创建一个辅助空间 left--right上有多少个数就开多大的空间
    int help[right - left + 1];
    int i = 0;        // 提供给help使用的变量
    int p1 = left;    // 左侧数据的下标
    int p2 = mid + 1; // 指向mid+1位置
    // 判断p1和p2是否越界,如果都不越界
    // 那么谁小谁就拷贝到数组help中去
    while (p1 <= mid && p2 <= right)
    {
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    // 继续判断是否有没有拷贝完毕的数据
    // 可能是左半部分的p1没有拷贝完毕
    while (p1 <= mid)
    {
        help[i++] = arr[p1++];
    }
    // 也可能是右侧的p2没有拷贝完毕
    while (p2 <= right)
    {
        help[i++] = arr[p2++];
    }
    // 将help上面有序的数据拷贝到原数组上去,就得到了区间上有序数据
    for (i = 0; i < (sizeof(help) / sizeof(help[0])); i++)
    {
        arr[left + i] = help[i];
    }
}

int main()
{
    int arr[] = {12, 11, 13,2312,123,556,887, 5, 6, 7};
    int arr_size = sizeof(arr) / sizeof(arr[0]);

    printf("原始数组:\n");
    for (int i = 0; i < arr_size; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    mergeSort(arr, 0, arr_size - 1);

    printf("排序后的数组:\n");
    for (int i = 0; i < arr_size; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

快速排序(重点讲解)

时间复杂度O(nlogn)
空间复杂度O(n)
快速排序是我们最常用的排序算法,也是性能比较好的一种时间算法。
因此我会重点讲解快速排序的实现思路。
在上面我们提到了归并排序,其实现的思想是将对整个数组的操作,通过递归的方式,变为对一小块区间的排序操作,也就是说,合理的使用递归等过程,是可以优化性能的。那么快排也可以。
可以思考如下一个场景,假设有一个数组,元素内容是:1,3,5,3,4,6,7,1,2,6。
假设我们有一个需求,是通过给定一个数据,然后要求我们的代码使得数组中的小于等于这个数据的元素出现在这个元素的左边,而如果是大于这个数据的元素,出现在数组的右边。
也就是相当于我们通过给定的这个数,对数组进行了一个划分,对于给出的数target,恒有
数组左侧于target的数据,小于target,数组右侧于target的数据,大于target。
那么很容易通过双指针的方式得到这样子的代码:

 //指定一个数据 然后比这个数据大的数据放在右边,比这个数据小于等于的放在左边。
   void smallLeftBigRight(int arr[],int target){
        int slow = 0;
        int fast = 0;
        while (fast<arr.length){
            if (arr[fast]<=target){
                arr[slow]^=arr[fast];
                arr[fast]^=arr[slow];
                arr[slow]^=arr[fast];
                slow++;  
            }
            fast++;
        }
    }

输入一个数组之后,我们就可以发现,数组中某个位置左右的数据是小于和大于我们给出的数据的,如下:
在这里插入图片描述
可以发现,经过我们简单的这样子的操作,数据好像看起来有了那么一点顺序。
是的,快排就是基于这样子的一个思想:设定一个基准,要求基准值左边的数据小于等于基准值,基准值右边的数据大于基准值。并且通过递归的方式,直到所有的区间符合要求。
那么如果我们要实现上面的说法,应该如何实现呢:
实现思路1:
依旧是上面的数组,我们选定最后的一个数据为基准(设定为target),然后要求小于等于这个数据的出现在这个数据的左边,大于这个数据的出现在这个数据的右边。
然后我们通过遍历的方式,从左到右遍历这个数组,如果遇到的值小于target,就继续后移,因为合理,如果遇到的值大于target,我们就将这个值交换到数组的末尾去,这样子大的值就放到后面了,当然,如果是这种情况,由于我们不确定换过来的数据是否大于target,我们的指针是不能增加的,然后在进行一次上面的比较才确定是否增加。循环往复,直到右边交换的值都已经比当前target大。
那么此时我们就按照上面的代码中得到了这样子一个部分有序的数据了,然后此时我们将大于这个数据的第一个数据与这个最后一个数据进行交换(交换后target的索引为index),那么我们此时就得到了,这个target数据左边的数据都小于这个数据,右边的都大于这个数据。
然后我们继续缩短这个范围,根据这个数据划分出来的左右边界,继续执行这个流程,左边的那一边从最左边的索引开始left,然后右侧范围为index, 而右边也重复这样子的过程,其左边界为index+1,右边界为right。不断执行这样子的递归操作,直到左指针和右指针碰撞,我们就可以返回。
按照这样子的思路,我们可以发现,其时间复杂度最差为O(n2),因为在数据高度有序的情况下,例如:1,2,3,4,5。那么我们的过程其实每次都是这个数据自己和自己交换,因为左边的都是比它小的。大概的一个流程如下:
在这里插入图片描述
所以,按照我们上面的意思,快排的时间复杂度在这个基准值非常差劲的时候,时间复杂度
O(n2)。压根就不快,所以我们需要解决基准值很偏的问题。
代码实现如下,我这里设定最左边的数据为中轴。

 /**
     * 下面是对代码的逐步解释:
     * <p>
     * public static int[] quickSortX(int[] arr, int start, int end):这是快速排序的入口函数,
     * 它接受一个整数数组 arr 以及排序范围的起始位置 start
     * 和结束位置 end 作为参数,并返回排序后的整数数组。
     * <p>
     * int pivot = arr[start];:选择数组中的第一个元素作为中轴值(pivot)。
     * <p>
     * int left = start; 和 int right = end;:定义两个指针,left 从左向右移动,right 从右向左移动,
     * 用于在数组中找到需要交换的元素。
     * <p>
     * 下面的 while (left < right) 循环是快速排序的核心部分,它在数组中找到需要交换的元素,
     * 以确保中轴值左边的元素都小于等于中轴值,中轴值右边的元素都大于等于中轴值。这个循环包含以下几个部分:
     * <p>
     * 第一个 while 循环:从左边开始,找到一个大于或等于中轴值的元素。
     * 第二个 while 循环:从右边开始,找到一个小于或等于中轴值的元素。
     * 如果找到的左边元素等于右边元素,则继续将 left 向右移动,以避免出现无限循环。
     * 否则,交换左边元素和右边元素的值,确保左边元素小于中轴值,右边元素大于中轴值。
     * 之后,代码检查是否有需要递归排序的左半部分和右半部分。如果左半部分的起始位置小于左指针的前一个位置,
     * 递归调用 quickSortX 对左半部分进行排序。同样,如果右半部分的结束位置大于右指针的后一个位置,递归调用
     * quickSortX 对右半部分进行排序。
     * <p>
     * 最后,函数返回排序后的数组 arr。
     *
     * @param arr
     * @param start
     * @param end
     * @return
     */
    int[] quickSortX(int arr[], int start, int end) {
        int pivot = arr[start];
        int left = start;
        int right = end;
        //范围合法性
        while (left < right) {
            //设定pivot中轴值左边的数据要求比pivot小
            while (left < right && arr[left] < pivot) {
                left++;
            }
            //设定pivot中轴值右边的数据要比pivot大
            while (left < right && arr[right] > pivot) {
                right--;
            }
            //循环停止时 存在  arr[right]<=pivot>=arr[left]
            //如果等于 那么继续让left指针后移一位
            if (left < right && arr[left] == arr[right]) {
                left++;
            } else {//否则就是直接交换他们两个的值即可
                //此时存在 arr[left]<pivot<arr[right]
                //交换数据
                int temp = arr[left];
                arr[left] = arr[right];
                arr[right] = temp;
            }
        }
        if (start < left - 1) {
            arr = quickSortX(arr, start, left - 1);
        }
        if (right + 1 < end) {
            arr = quickSortX(arr, right + 1, end);
        }
        return arr;
    }

这个时候,按照我们上面的思想,我们肯定希望我们的中轴值没有那么偏僻,所以我们考虑使用一个随机的方式,生成我们中轴的位置。那么我们按照master方式,可以得到最快的效率O(nlogn)。
所以我们就有了目前使用的快速排序,思路如下:
实现思路2:

 /**
     * void quickSort(int[] arr):这是公共的快速排序入口函数。
     * 它接受一个整数数组 arr 并检查是否需要排序。如果数组为空或只包含一个元素,它就不执行排序。否则,它调用
     * quickSortO2 来进行快速排序。
     * <p>
     * void quickSortO2(int[] arr, int left, int right):这个函数执行实际的快速排序算法。
     * 它接受数组 arr 以及排序的范围从 left 到
     * right。该函数使用了随机选择中轴值的方法,然后调用 partition 函数来划分数组,并递归地对左右两部分进行排序。
     * <p>
     * swap 函数用于交换数组中的两个元素。这个函数通过位运算进行交换,是一种非常快速的方法。
     * <p>
     * int[] partition(int[] arr, int left, int right):这个函数用于处理 arr[left...right]
     * 上的数据,将数组按照中轴值进行划分。它返回一个包含两个元素的整数数组,
     * 表示等于中轴值的区域的左边界和右边界。这个函数采用双指针法,其中 lessBound
     * 表示小于区的右边界,moreBound 表示大于区的左边界。
     * <p>
     * 首先,通过将 arr[right] 作为中轴值,初始化 lessBound 和 moreBound。
     * 使用 left 指针从左到右遍历数组元素。
     * 如果当前元素小于中轴值,将其与 lessBound 右边的元素交换,并将 lessBound 和 left 向右移动。
     * 如果当前元素大于中轴值,将其与 moreBound 左边的元素交换,并将 moreBound 向左移动。
     * 如果当前元素等于中轴值,只将 left 指针向右移动。
     * 最后,将 arr[right] 与 moreBound 处的元素交换,将数组划分为小于、等于和大于中轴值的三个部分。
     * 返回 lessBound + 1 和 moreBound,它们分别表示等于区的左边界和右边界。
     * <p>
     * 这里,对于partition方法,我的实现和思考思路如下:
     * <p>
     * 初始化 lessBound 为 left - 1,即 lessBound 初始为-1,表示小于区的右边界。
     * <p>
     * 初始化 moreBound 为 right,即 moreBound 初始为10,表示大于区的左边界。
     * <p>
     * 使用 left 指针从左到右遍历数组元素。
     * <p>
     * 当 arr[left](当前元素)小于 arr[right](中轴值)时,执行以下操作:
     * 交换 arr[left] 和 arr[lessBound + 1],然后增加 lessBound 和 left。
     * 这表示我们将小于区的右边界向右扩展一个位置,并将当前元素放入小于区。
     * 当 arr[left] 大于 arr[right] 时,执行以下操作:
     * 交换 arr[left] 和 arr[moreBound - 1],然后减少 moreBound。
     * 这表示我们将大于区的左边界向左扩展一个位置,并将当前元素放入大于区。
     * 当 arr[left] 等于 arr[right] 时,只将 left 指针向右移动,因为相等的元素将留在等于区。
     * 最终,left 指针遍历整个数组,将数组划分为三个部分:小于区、等于区和大于区。
     * <p>
     * 为了完成划分,将 arr[right](中轴值)与 arr[moreBound](大于区的左边界)进行交换。这将把中轴值放到正确的位置。
     * <p>
     * 返回一个包含两个元素的数组,[lessBound + 1, moreBound]。这个数组表示等于区的左边界和右边界。
     * <p>
     * 其中,对于 arr[left] > arr[right] 这种情况,我并没有left++,是因为:
     * 当前元素 arr[left] 大于中轴值 arr[right] 时,我们将它放入大于区,即 arr[moreBound - 1] 处。
     * 这意味着 moreBound 表示大于区的左边界。
     * <p>
     * 通过减少 moreBound 的值,我们将大于区的左边界向左移动,同时保持 left 指针不变。
     * 这是因为当前元素 arr[left] 已经被放入大于区,而在下一次迭代中,我们需要继续检查新的 arr[left]
     * 是否大于中轴值,以确保大于区包含所有大于中轴值的元素。
     * <p>
     * 所以,left 指针只在当前元素小于中轴值时执行 ++ 操作,表示将当前元素放入小于区,而在当前元素大于中轴值时,
     * 只需要更新大于区的左边界 moreBound,而不需要改变 left 指针。
     *
     * @param arr
     */
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSortO2(arr, 0, arr.length - 1);
    }

    public static void quickSortO2(int[] arr, int left, int right) {
        if (left < right) {
            swap(arr, left + (int) (Math.random() * (right - left + 1)), right);
            int[] p = partition(arr, left, right);
            quickSortO2(arr, left, p[0] - 1); //< 区域
            quickSortO2(arr, p[1] + 1, right); //> 区域
        }
    }

    //当前方法用于处理arr[left...right]上面的数据
    //默认以arr[right]做划分
    //返回等于区域(左边界、右边界),所以返回一个长度为2的数组res,res[0],res[1]
    public static int[] partition(int[] arr, int left, int right) {
        int lessBound = left - 1;//小于区右边界
        int moreBound = right; //大于区左边界
        while (left < moreBound) {//left表示当前数据的位置 arr[right] -->划分值
            if (arr[left] < arr[right]) { //当前数据小于划分值
                swap(arr, ++lessBound, left++);
            } else if (arr[left] > arr[right]) {
                swap(arr, --moreBound, left);
            } else {
                left++;
            }
        }
        swap(arr, moreBound, right);
        return new int[]{lessBound + 1, moreBound};
    }

    public static void swap(int[] arr, int i, int j) {
        //特别注意 如果i==j,那么会导致数据被抹除
        if (i == j) {
            return;
        }
        arr[i] ^= arr[j];
        arr[j] ^= arr[i];
        arr[i] ^= arr[j];
    }

对于记忆的话,还是选择可读性比较好的方法1吧,追求性能的时候可以考虑使用方法2。

堆排序(重点理解)

堆排序的时间复杂度是稳定的O(nlogn)
空间复杂度为O(1)
堆排序的思想是基于完全二叉树的。
堆排序会使得数据满足大顶堆或者小顶堆的特性。大顶堆的意思就是非叶子节点的值大于叶子节点。小顶堆则相反。
因此对于一个堆,其插入数据不符合堆的特性的时候,是需要进行堆化的,也就是会将这个数据进行排序,移动到合理的位置,使其满足大顶堆的特性。

堆排序的详细步骤如下:
建立堆:从数组的中间元素开始,从右至左,或者从下至上,对每个元素执行下沉操作(即将元素下沉到适当的位置),以确保树的每个分支满足堆的性质。这将创建一个有效的最大堆或最小堆,取决于排序需求。

排序:堆建立完成后,根节点包含堆中的最大(或最小)元素。将根节点与堆的最后一个元素交换,然后缩小堆的范围,即排除掉最大(或最小)元素。接着,对根节点执行一次下沉操作,以确保新的根节点仍然是堆中的最大(或最小)元素。重复这个步骤,直到堆为空。

重复:重复步骤2,直到整个数组被排序。

大部分讲解堆的博客中都会明确告诉你,堆排序是需要在数组的末尾不断缩短边界的,把堆顶元素和数组末尾元素进行交换,然后在进行一次堆化,不断循环往复,就可以得到一个有序的数组。
这就是堆排序的精髓。
当然当然,堆其实最最最最重要的不是堆排序,而是堆的结构,合理利用堆的结构可以解答非常非常非常多的题目。
比如如下这题:
考研408真题

#include <stdio.h>

// 升序排序堆排序 --- 使用大顶堆
void adjustHeap(int arr[], int i, int length) {
    int temp = arr[i]; // 将当前非叶子结点保存
    for (int k = 2 * i + 1; k < length; k = k * 2 + 1) {
        if (k + 1 < length && arr[k] < arr[k + 1]) { // 判断左孩子大还是右孩子大
            k++; // 如果右孩子大就++获得右孩子的索引
        }
        if (arr[k] > temp) { // 判断孩子大还是当前节点大
            arr[i] = arr[k]; // 将更大的向上排
            i = k; // 大的元素就跑到了非叶子结点的索引去,然后让 i 去更小数据的索引
        } else {
            break;
        }
    }
    arr[i] = temp; // 大的数据被移上去了,但是其原有位置的数据还没有被修改
}

// 降序排序堆排序 --- 使用小顶堆
void adjustHeapDesc(int arr[], int i, int length) {
    int temp = arr[i]; // 将当前非叶子结点保存
    for (int k = 2 * i + 1; k < length; k = k * 2 + 1) {
        if (k + 1 < length && arr[k] > arr[k + 1]) { // 判断左孩子大还是右孩子大
            k++; // 如果右孩子小就++获得右孩子的索引
        }
        if (arr[k] < temp) { // 判断孩子小还是当前节点小
            arr[i] = arr[k]; // 将更小的向上排
            i = k; // 小的元素跑到了非叶子结点的索引,然后让 i 去更大数据的索引
        } else {
            break;
        }
    }
    arr[i] = temp; // 小的数据被移上去了,但是其原有位置的数据还没有被修改
}

void heapSort(int arr[], int length) {
    // 构建小顶堆
    for (int i = length / 2 - 1; i >= 0; i--) {
        adjustHeapDesc(arr, i, length);
    }
    
    printf("初始构造小顶堆: ");
    for (int i = 0; i < length; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 将堆顶元素与末尾元素进行交换,将最小数据沉到末尾
    // 重新调整结构,使其继续满足小顶堆性质
    for (int j = length - 1; j > 0; j--) {
        int temp = arr[0];
        arr[0] = arr[j];
        arr[j] = temp;

        // 重新调整堆,排除已排序部分,使其继续满足小顶堆性质
        adjustHeapDesc(arr, 0, j);
    }
    
    printf("小顶堆排序后的数组(降序排列): ");
    for (int i = 0; i < length; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {4, 10, 3, 5, 1};
    int length = sizeof(arr) / sizeof(arr[0]);

    printf("原始数组: ");
    for (int i = 0; i < length; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    heapSort(arr, length);

    return 0;
}


408考研各数据结构C/C++代码(Continually updating)

408考研各数据结构C/C++代码(Continually updating)
这个模块是我应一些朋友的需求,希望我能开一个专栏,专门提供考研408中各种常用的数据结构的代码,并且希望我附上比较完整的注释以及提供用户输入功能,ok,fine,这个专栏会一直更新,直到我认为没有新的数据结构可以讲解了。
目前我比较熟悉的数据结构如下:
数组、链表、队列、栈、树、B/B+树、红黑树、Hash、图。
所以我会先有空更新出如下几个数据结构的代码,欢迎关注。 当然,在我前两年的博客中,对于链表、哈夫曼树等常用数据结构,我都提供了比较完整的详细的实现以及思路讲解,有兴趣可以去考古。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1095935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jupyter Notebook的使用

以管理员身份运行 命令行显示内容含义 JupyterLab application directory is D:\Program Files\anaconda\share\jupyter\lab↑这一行是JupyterLab的地址 Serving notebooks from local directory: C:\Users\Sylvia\Documents↑这一行是文件的保存地址。其中Users中文文件夹名是…

【C++STL基础入门】stack栈的增删查等操作的使用

文章目录 前言一、stack元素访问1.1 元素访问之top 二、stack修改2.1 stack修改之push()函数2.2 stack修改之pop()函数2.3 stack修改之swap()交换函数2.4 stack修改之emplace()在顶部原位构造元素 总结 前言 在C的标准模板库(STL)中&#xff0c;stack&#xff08;栈&#xff0…

zabbix监控keepalived主备状态以及脑裂

zabbix监控keepalived主备状态以及脑裂 文章目录 zabbix监控keepalived主备状态以及脑裂环境说明&#xff1a;1.配置keepalived监控主备状态的脚本在master主机上编写脚本在slave主机上编写脚本 2.配置keepalived加入监控脚本的配置2.1.配置主keepalived配置文件2.2.配置备keep…

Godot C#连接信号不能像GDScirpt一样自动添加代码

前言 我网上找了好久&#xff0c;发现Godot 对于C# 的支持还有待增强 使用c#脚本有办法像gds那样连接节点自带信号时自动生成信号吗&#xff1f; 百度贴吧 Godot C# How To, Episode 9. Signals With Parameters | Godot Mono 解决方案 把信号拉长&#xff0c;看他的属性 修…

Python与JAVA有何区别?

多年来&#xff0c;程序员一直在寻找一种编程语言&#xff0c;使用这种编程语言&#xff0c;你只需编写一次应用程序&#xff0c;即可让它运行在多种平台之下。Java就是一种跨平台的编程语言&#xff0c;为此Java需要使用一些技巧&#xff0c;相关内容你会在本书后面了解到。而…

【Java 进阶篇】JavaScript DOM 编程:理解文档对象模型

在 web 开发中&#xff0c;DOM&#xff08;文档对象模型&#xff09;是一个重要的概念。DOM 是一种将网页文档表示为树状结构的方式&#xff0c;允许开发者使用 JavaScript 来访问和操作网页的内容。本篇博客将详细介绍 DOM&#xff0c;包括什么是 DOM、如何访问 DOM 元素、如何…

华为云云耀云服务器L实例评测|企业项目最佳实践之压测 (十一)

十二、云服务器压测&#xff1a; 服务器压测可以选择工具sysbench&#xff0c;它可以模拟几千个线程并发的访问实例。作为一款广泛使用的开源模块化的、跨平台、多线程基准测试工具&#xff0c;sysbench主要用于评估服务器系统在不同负载条件下的性能表现。 sysbench具有简单易…

PYTHON进阶-面向对象编程

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

基于Java的家电销售网站管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域…

C++位图和布隆过滤器

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容介绍C中的位图和布隆过滤器模拟实现和简单的应用 文章目录 …

求直角三角形第三点的坐标

文章目录 求直角三角形第三点的坐标1. 原理2. 数学公式3. 推导过程 求直角三角形第三点的坐标 1. 原理 已知内容有&#xff1a; P1、P2 两点的坐标&#xff1b; dis1 为 P1与P2两点之间的距离&#xff1b; dis2 为 P2与P3两点之间的距离&#xff1b; 求解&#xff1a; …

10、网络防火墙的设置

1、查看防火墙是否开启 systemctl status firewalld 此图表示防火墙已开启&#xff0c;如果未开启可使用systemctl start firewalld开启 2、查看已开启的端口 firewall-cmd --list-ports 默认无打开的端口 3、打开80端口 firewall-cmd --zonepublic --add-port80/tcp --p…

stm32备份

存储器的分类&#xff1a; 存储器首先根据断电后存储的数据是否会丢失&#xff0c;可以分为易失存储器和非易失存储器&#xff0c;易失存储器主要应用于内存&#xff0c;非易失存储器主要用于外存。 易失存储器以RAM随机存储器为代表&#xff0c;随机的含义是存储器中的数据读取…

EfficientDet: Scalable and Efficient Object Detection

CVPR2020 V7 Mon, 27 Jul 2020 引用量&#xff1a;243 机构&#xff1a;Google 贡献&#xff1a;1>提出了多尺度融合网络BiFPN 2>对backbone、feature network、box/class prediction network and resolution进行复合放缩&#xff0c;有着不同的…

Linux第4章-目录结构

引子 在Linux中&#xff0c;一切皆文件&#xff01;&#xff01;&#xff01; 在Linux中&#xff0c;一切皆文件&#xff01;&#xff01;&#xff01; 在Linux中&#xff0c;一切皆文件&#xff01;&#xff01;&#xff01; 基本介绍 1.Linux的文件系统&#xff0c;是采用…

LoRA 是如何工作的?

概述 纯笔记 LoRA的原理 LoRA其实是对稳定扩散模型最关键的部分进行了微小的改变。 这个关键的部分叫&#xff1a;cross-attention layers – 交叉注意力层。 研究人员发现&#xff0c;对这关键部分进行微调就足以实现良好的训练。 上面黄色部分&#xff0c;QKV 部分就是&a…

Python进阶之迭代器

文章目录 前言一、迭代器介绍及作用1.可迭代对象2. 迭代器 二、常用函数和迭代器1.常用函数2.迭代器 三、总结结束语 &#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注…

ELK 日志分析系统介绍与部署

目录 一、ELK 简介: 1.开源工具介绍&#xff1a; 2.其它组件&#xff1a; 2.1 Filebeat&#xff1a; 2.2 Fluentd&#xff1a; 2.3 缓存/消息队列&#xff08;redis、kafka、RabbitMQ等&#xff09;&#xff1a; 3. filebeat 结合 logstash 带来好处&#xff1a; 二、为什么要…

zookeeper源码学习笔记(一)

一、缘起 1、CP还是AP 作为一个在大数据行业工作了7&#xff5e;8年的老兵&#xff0c;在被问到zookeeper和CAP时&#xff0c;竟然有些恍惚&#xff0c;AP还是CP&#xff1f; 看了一些博文&#xff0c;答案几乎都是CP&#xff1f; zookeeper的实现中&#xff0c;P是一定的&…

【C语言】.c源文件从编译到链接生成可执行程序的过程

本篇文章目录 1. 过程概览2. 编译与链接2.1 预编译/预处理2.2 编译2.3 汇编2.4 链接 3. 执行/运行环境 1. 过程概览 编译到链接是c语言的翻译环境&#xff0c;c语言还有执行环境。 组成一个程序的每个源文件通过编译过程分别转换成目标代码&#xff08;object code&#xff09;…