数据结构与算法之美【9】-排序

目录

一、概述

二、排序类型

1、冒泡排序

2、插入排序 

3、希尔(shell)排序

4、选择排序 

5、归并排序

6、快速排序

7、桶排序

8、计数排序

9、基数排序

10、堆及堆排序


一、概述

 在开始介绍几种排序算法之前,先介绍几种排序相关的概念,防止后面介绍时,有理解偏差:

  • 稳定性:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,则说这种排序是稳定的,否则就是不稳定的。
  • 原地排序:指的是在排序过程中不申请多余的存储空间或者申请很少的空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序
  • 有序度:指的是待排序的序列a中,满足i < j且a[i] <= a[j]要求的元素对的个数。
  • 满有序度:指的是待排序的序列a中,所有元素对都满足i < j且a[i] <= a[j],即完全有序的序列。即n*(n-1)/2
  • 逆序度:指的是待排序的序列a中,满足i < j且a[i] > a[j]要求的元素对的个数,和有序度刚好相反。
  • 逆序度 = 满有序度 - 有序度

八大排序,三大查找是 数据结构中非常基础的知识点,在这里为了复习顺带总结了一下常见的八种排序算法。
常见的八大排序算法,他们之间关系如下:

在这里插入图片描述

 各排序算法的时间复杂度及空间复杂度如下,理解不了就背下来:

在这里插入图片描述

二、排序类型

1、冒泡排序

实现思想:操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足就则让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。它是一个稳定的排序算法

复杂度分析:空间复杂度为O(1),时间复杂度为:

  • 最好情况时间复杂度为O(n)
  • 最坏情况时间复杂度为O(n^2)
  • 平均情况时间复杂度也为O(n^2)

实际上,上述的冒泡过程还可以优化。当某次冒泡操作没有执行数据交换,说明此时序列已经达到完全有序,不需要再继续执行后续的冒泡操作了。例如:

代码示例:

// 冒泡排序,a表示数组,n表示数组大小
void bubbleSort(int* a, int n) {
    if (n <= 1) return;

    for (int i = 0; i < n; ++i) {
        // 提前退出冒泡循环的标志位
        bool flag = false;
        for (int j = 0; j < n - i - 1; ++j) {
            if (a[j] > a[j + 1]) { // 交换
                int tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
                flag = true;  // 表示有数据交换      
            }
        }
        if (!flag)// 没有数据交换,提前退出
        {
            break;
        }
    }
}

2、插入排序 

实现思想:先将数组中的数据分为两个区间---已排序区间和未排序区间。初始时,已排序区间只有一个元素,即数组的第一个元素,然后依次取未排序区间中的元素,将其插入到已排序区间中合适的位置。重复这个过程,直到未排序区间中元素为空,则排序完成。是一个稳定的排序算法

如下图所示,要排序的数据是 4,5,6,1,3,2,其中左侧为已排序区间,右侧是未排序区间。

插入操作的次数总是固定的,就等于逆序度,例如下面元素的初始逆序度为10,则需要进行10次插入操作。

复杂度分析:空间复杂度为O(1),时间复杂度为:

  • 最好情况时间复杂度为O(n)
  • 最坏情况时间复杂度为O(n^2)
  • 平均情况时间复杂度也为O(n^2)

示例代码:

// 插入排序,a表示数组,n表示数组大小
void insertionSort(int* a, int n) {
    if (n <= 1) return;

    for (int i = 1; i < n; ++i) {
        int value = a[i];
        int j = i - 1;
        // 查找插入的位置
        for (; j >= 0; --j) {
            if (a[j] > value) {
                a[j + 1] = a[j];  //不满足条件,数据移动,空出位置
            }
            else {
                break; //满足条件,跳出
            }
        }
        a[j + 1] = value; // 插入数据
    }
}

3、希尔(shell)排序

实现思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组,所有距离为d1的倍数的数据放在同一个组中,并在各组内进行直接插入排序。组内排序完成后,取第二个增量d2(d2<d1),重复刚刚的步骤,直至所取的增量dt==1(dt<dt-1<…<d2<d1),此时排序完成。是一个不稳定的排序算法

复杂度分析:空间复杂度为O(1),时间复杂度为:

  • 最好情况时间复杂度为O(n)
  • 最坏情况时间复杂度为O(n^2)
  • 平均情况时间复杂度也为O(n^1.3)

代码示例:

void shellSort(int* ary, int n)
{
    int j;
    int increment = n / 2;//默认增量(可根据实际情况选择)
    int key;
    while (increment >= 1)//增量为1时,排序完成
    {
        for (int i = increment; i < n; i++)//从increment开始遍历所有元素
        {
            key = ary[i];     //key为同一增量组的未排序队列的第一个元素值
            j = i - increment;//j为同一增量组的已排序队列的最后一个元素值
            //插入排序
            while (j >= 0)
            {
                if (key < ary[j]) //不满足条件,则移动
                {
                    ary[j + increment] = ary[j];
                }
                else 
                {
                    break;
                }
                j = j - increment;
            }
            ary[j + increment] = key; //插入完成,赋值
        }

        increment = increment / 2; //缩小增量
    }
}

4、选择排序 

实现思想:选择排序算法的实现思想和插入排序有写类似,也是分为两个区间---已排序区间和未排序区间。不同的是选择排序每次会先从未排序区间中找到最小的元素,然后和未排序区间中的第一个元素交换。是一个不稳定的排序算法

复杂度分析:空间复杂度为O(1),时间复杂度为:

  • 最好情况时间复杂度为O(n^2)
  • 最坏情况时间复杂度为O(n^2)
  • 平均情况时间复杂度也为O(n)

示例代码:

void selectSort(int a[], int n)
{
     // 寻找第i个小的数值
    for (int i = 0; i < n - 1; i++)
    {
        int temp = 0;
        int index = i; // 用来保存最小值得索引
        // 从未排序区间内找最小元素
        for (int j = i + 1; j < n; j++)
        {
            if (a[index] > a[j])
            {
                index = j;
            }
        }
        // 将找到的第i个小的数值放在已排序区间的末尾
        temp = a[index];
        a[index] = a[i];
        a[i] = temp;
    }
}

5、归并排序

实现思想:将一个要排序数组,使用递归方法将其从中间分成前后两部分,直至只剩一个个元素时,再依次将两部分合并在一起的同时进行排序。这也就是我们平时所说的分治思想。归并排序是一个稳定的排序算法。

归并排序

既然是用递归实现,那我们就按照数据结构与算法之美【8】-递归算法所说的步骤进行分析,即先分析得出递推公式,然后找到终止条件,最后将递推公式翻译成递归代码。

递推公式:

merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))

终止条件:

p >= r 不用再继续分解

 我来解释一下这个递推公式:

merge_sort(p…r) 表示,给下标从 p 到 r 之间的数组排序。我们将这个排序问题转化为了两个子问题,merge_sort(p…q) 和 merge_sort(q+1…r),其中下标 q 等于 p 和 r 的中间位置,也就是 q = (p+r)/2。当下标从 p 到 q 和从 q+1 到 r 这两个子数组都排好序之后,我们再将两个有序的子数组合并在一起,这样下标从 p 到 r 之间的数据就也排好序了。

另一个难点是合并两个有序的数组,其实现思想是:申请一个临时数组 tmp,大小与 A[p...r]相同。我们用两个游标 i 和 j,分别指向 A[p...q]和 A[q+1...r]的第一个元素。比较这两个元素 A[i]和 A[j],如果 A[i]<=A[j],我们就把 A[i]放入到临时数组 tmp,并且 i 后移一位,否则将 A[j]放入到数组 tmp,j 后移一位,即不断比较前后部分中最小的元素值。继续上述比较过程,直到其中一个子数组中的所有数据都放入临时数组中,再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,临时数组中存储的就是两个子数组合并之后的结果了。最后再把临时数组 tmp 中的数据拷贝到原数组 A[p...r]中。

复杂度分析:空间复杂度为O(n),时间复杂度为:

  • 最好情况时间复杂度为O(nlogn)
  • 最坏情况时间复杂度为O(nlogn)
  • 平均情况时间复杂度也为O(nlogn)

示例代码:

/归并排序 外部接口
void mergeSort(int* array,int len) {
    int* temp = new int[len];
    sort(array, temp, 0, len - 1);
}

//对array的left~right进行排序
void sort(int* array, int* temp, int left, int right) {
    // 中间元素
    int mid = (left + right) / 2;
    
    // 先对左边元素进行排序
    if (left < mid) {
        // 递归排序左边元素至中间元素
        sort(array, temp, left, mid);
    }
    
    // 再对右边元素进行排序
    if (mid + 1 < right) {
        // 递归排序中间元素至右边的元素
        sort(array, temp, mid + 1, right);
    }

    //两边数组都排序完成后,将其合并
    merge(array, temp, left, mid, right);
}

//借助temp数组,合并mid元素左右的元素
void merge(int* array, int* temp, int left, int mid, int right) {
    // 记录左边起始位置
    int i = left;
    // 记录右边结束位置
    int j = mid + 1;

    // 临时变量
    int t = 0;
    while (i <= mid && j <= right) {
        // 比较左右两边元素,将较小的元素先添加到temp数组中
        if (array[i] <= array[j]) {
            temp[t++] = array[i++];
        }
        else {
            temp[t++] = array[j++];
        }
    }

    while (i <= mid) {
        // 将左边剩余元素添加到temp数组中
        temp[t++] = array[i++];
    }
    while (j <= right) {
        // 将右边剩余元素添加到temp数组中
        temp[t++] = array[j++];
    }

    t = 0;
    // 此时left至right完成排序
    // 将temp中的元素按照原先位置全部拷贝到原数组中
    // 原数组left至right完成怕排序
    while (left <= right) {
        array[left++] = temp[t++];
    }
} 

6、快速排序

快排利用的也是分治思想,但和归并排序不同,它是一个不稳定的排序算法。:

  1. 先从数列中取出一个数作为基准数(基准数的选择至关重要,影响复杂度),进行分区。
  2. 分区:将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
  3. 再对左右区间重复第二步,直到各区间只有一个数。  

下面使用了一个很巧妙的方法,将一个数组进行分区,使其空间复杂度为O(1)。 如下图所示:

假设最开始的基准数据为数组第一个元素23,则首先用一个临时变量去存储基准数据,即tmp=23;然后分别从数组的两端扫描数组,设两个指示标志:low指向起始位置,high指向末尾.

这里写图片描述

 首先从后半部分开始如果扫描到的值大于基准数据就让high减1,继续遍历下一个,如果发现有元素比该基准数据的值小(如上图中18<=tmp),就将high位置的值赋值给low位置 ,即a[0] = a[high],结果如下:

这里写图片描述

然后开始从前往后扫描,如果扫描到的值小于基准数据就让low加1,如果发现有元素大于基准数据的值(如上图46=>tmp),就再将low位置的值赋值给high位置的值,指针移动并且数据交换后的结果如下:

这里写图片描述

然后再开始从后向前扫描,原理同上,发现上图11<=tmp,则将low位置的值赋值为high位置的值 ,结果如下:

这里写图片描述

然后再开始从前往后遍历,直到low=high结束循环,此时low或high的下标就是基准数据23在该数组中的正确索引位置.进行赋值即可a[low] = temp 如下图所示.

这里写图片描述

这样一遍走下来,就把基准数大的都放在基准数的右边,把比基准数小的放在基准数的左边,之后采用递归的方式再分别对前半部分和后半部分再进行这种方式排序,直到low和high相等。

复杂度分析:空间复杂度为O(nlog2n),时间复杂度为:

  • 最好情况时间复杂度为O(nlog2n)
  • 最坏情况时间复杂度为O(n^2)
  • 平均情况时间复杂度也为O(nlog2n)

示例代码:

void quickSort(int* arr, int low, int high) {
    if (low >= high) {
          return;
    }
     // 找寻基准数据的索引
     int index = getIndex(arr, low, high);

     // 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
      quickSort(arr, low, index - 1);
      quickSort(arr, index + 1, high);

 }
int getIndex(int* arr, int low, int high) {

        // 基准数据,这里选择的是数组的第一个元素,但不推荐
        int tmp = arr[low];

        while (low < high) {
            // 当队尾的元素大于等于基准数据时,不需要移动元素,仅仅向后挪动high指针,
            // 继续遍历下一个
            while (low < high && arr[high] >= tmp) {
                high--;
            }

            // 找到了小于tmp的元素了,转移arr[high],将其赋值给low
            arr[low] = arr[high];

            // 当队首的元素小于基准数据时,不需要移动元素,仅仅向前挪动high指针,
            // 继续遍历下一个
            while (low < high && arr[low] <= tmp) {
                low++;
            }

            // 当队首元素大于等于tmp时,需要将其赋值给high
            // 此时arr[high]已被转移,不用担心数据丢失
            arr[high] = arr[low];

        }
        // 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
        arr[low] = tmp;
        return low; // 返回tmp的正确位置
}

7、桶排序

实现思想:将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

复杂度分析:如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶内,每个桶里就有 k=n/m 个元素。每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度就是 O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)。但是如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了,在极端情况下,如果数据都被划分到一个桶里,那就退化为 O(nlogn) 。桶排序的稳定性取决于桶内排序使用的算法。

桶排序一般适应于那种要排序的数据很容易就能划分成 m 个桶,而且数据在各个桶之间的分布是比较均匀的场景。例如我们有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中,每个小文件中存储大约 100MB 的订单数据,我们就可以将这 100 个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。不过,你可能也发现了,订单按照金额在 1 元到 10 万元之间并不一定是均匀分布的 ,所以 10GB 订单数据是无法均匀地被划分到 100 个文件中的。有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。这又该怎么办呢?针对这些划分之后还是比较大的文件,我们可以继续划分,比如,订单金额在 1 元到 1000 元之间的比较多,我们就将这个区间继续划分为 10 个小区间,1 元到 100 元,101 元到 200 元,201 元到 300 元....901 元到 1000 元。如果划分之后,101 元到 200 元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。

代码示例:

//数据范围为1-99,分为3个桶
void BucketSort(int* arr, int n) {

    int buckets[3][33] = {0};
    int idx_0 = 0;
    int idx_1 = 0;
    int idx_2 = 0;

    // 1.计数,将数组arr中的元素放到桶中
    for (int i = 0; i < n; i++) {
        if (arr[i] <= 33) {
            buckets[0][idx_0] = arr[i];
            idx_0++;
        }
        else if (arr[i]> 33 && arr[i] <= 66) {
            buckets[1][idx_1] = arr[i];
            idx_1++;
        }
        else{
            buckets[2][idx_2] = arr[i];
            idx_2++;
        }
    }

    // 2.排序
    int currIdx = 0;
    for (int i = 0; i < 3; i++) {
        int len = 0;
        if (0 == i) {
            len = idx_0;
        }
        else if (1 == i)
        {
            len = idx_1;
        }
        else {
            len = idx_2;
        }
        //这里可以自行选择排序方式
        insertionSort(buckets[i], len);

        3.依次将桶内数据拷贝回原数组
        for (int j = 0; j < len; j++) {
            arr[currIdx] = buckets[i][j];
            currIdx++;
        }
    }
}

8、计数排序

实现思想:其实计数排序其实是桶排序的一种特殊情况。当要排序的 n 个数据,所处的范围并不大的时候,比如最大值是 k,我们就可以把数据划分成 k 个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。

例如高考查分数系统!我们知道高考的满分是750 分,最小是 0 分,这个数据的范围很小,所以我们可以分成 751 个桶,对应分数从 0 分到 750 分。然后假设你所在的省有 50 万考生,我们将这 50 万考生划分到这 751 个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。我们只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。因为只涉及扫描遍历操作,所以时间复杂度是 O(n)。

记住,计数排序只能用在数据量大,而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。

下面再以一个例子进行详细分析,假设我们有 8 个考生,分数在 0 到 5 分之间,这 8 个考生的成绩我们放在一个数组 A[8]中,它们分别是:2,5,3,0,2,3,0,3。

考生的成绩从 0 到 5 分,则我们使用 6个桶来表示这两个分数, 即数组 C[6],其中下标对应分数。不过,桶内存储的是对应考分的考生个数。此时我们只需要遍历一遍考生分数,就可以得到 C[6]的值:即考分为0的有两个考生,考分为1的有0个考生.......

接着我们对 C[6]数组顺序求和,C[6]存储的数据就变成了考分小于等于下标的考生个数,即C[k]里存储小于等于分数 k 的考生个数。此时数组C中存储的就是对应分数的位置。

然后我们先分配一个和源数据一样大小的数组R[8],用来存储排序后的队列,再去从尾到头依次遍历源数组,例如第一个数据为3,则3所属的桶为C[3],此时记录的偏移位置为7-1 = 6,所以我们可以直接将3放入R[6]中,然后记得此时桶C[3]应减去1,然后接着处理0....。当所有数据都遍历完成后,数组R[8]即为已排序后的数据,然后将R[8]赋值到A[8]即可。

复杂度分析:因为每个桶内的数据值都是相同的,省掉了桶内排序的时间。所以时间复杂度是 O(n)。

代码示例:

// 计数排序,A是数组,n是数组大小。假设数组中存储的都是非负整数。
void countingSort(int* A, int n) {
    if (n <= 1) {
        return;
    }

    // 查找数组中数据的范围
    int max = A[0];
    for (int i = 1; i < n; ++i) {
        if (max < A[i]) {
            max = A[i];
        }
    }

    int* C = new int[max + 1]; // 申请一个计数数组C,下标大小[0,max],并初始化
    for (int i = 0; i <= max; ++i) {
        C[i] = 0;
    }

    // 计算每个元素的个数,放入C中
    for (int i = 0; i < n; ++i) {
        C[A[i]]++;
    }

    // 依次累加,i为0是情况不需要累加
    for (int i = 1; i <= max; ++i) {
        C[i] = C[i - 1] + C[i];
    }

    // 临时数组R,存储排序之后的结果
    int* R = new int[n];

    // 从尾到头依次遍历源数据
    for (int i = n - 1; i >= 0; --i) {
        //index为A[i]在目标数组的位置
        int index = C[A[i]] - 1;
        //在目标数组内进行赋值
        R[index] = A[i];

        //赋值完成后,需要减去1,下次相同数据过来时,放在它前面
        C[A[i]]--;
    }

    // 将结果拷贝给A数组
    for (int i = 0; i < n; ++i) {
        A[i] = R[i];
    }

    delete []R;
    delete []C;
}

9、基数排序

实现思想:将所有待比较数值(注意,必须是正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始, 依次进行一次稳定排序,这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到 O(n) 了。例如假设我们有 10 万个手机号码,希望将这 10 万个手机号码从小到大排序,则可以使用基数排序。

有时候要排序的数据并不都是等长的,比如我们排序牛津字典中的 20 万个英文单词,最短的只有 1 个字母,最长的有 45 个字母,对于这种不等长的数据,我们可以把所有的单词补齐到相同长度,位数不够的可以在后面补“0”,因为根据ASCII 值,所有字母都大于“0”,所以补“0”不会影响到原有的大小顺序。这样就可以继续用基数排序了。

复杂度分析:时间复杂度为O(n)

示例代码:

int maxLens(int* data, int n)
{
    //默认长度为1
    int len = 1;
    //遍历数据,求得源数据的长度最大值
    for (int i = 0; i < n; i++)
    {
        //当前data[i]的长度
        int c = 1;
        //使用临时变量,保证源数据不被修改
        int p = data[i];
        while (p / 10)
        {
            p = p / 10;
            c++;
        }
        //如果data[i]的长度大于之前的长度,则更新len
        if (c > len) {
            len = c;
        }
            
    }
    return len;
}


void RadixSort(int* data, int n)
{
    int C[10];
    int* tmp = new int[n];
    int maxLen = maxLens(data, n);
    
    int r = 1;
    for (int i = 0; i < maxLen; i++)
    {
        // 使用计数排序
        for (int i = 0; i < 10; i++)//初始化
        {
            C[i] = 0;
        }
            
        for (int i = 0; i < n; i++) //记录每个桶的含有数目
        {
            //得到指定位的数字的个数
            int k = data[i] / r;
            int q = k % 10;
            C[q]++;
        }
        for (int i = 1; i < 10; i++)//计算位置
        {
            C[i] = C[i - 1] + C[i];
        }

        //从尾到头遍历源数据指定的位
        for (int j = n - 1; j >= 0; j--)
        {
            int k = data[j] / r;
            int q = k % 10;
            // 该数据在新数组中应该存储的位置
            int index = C[q] - 1;
            tmp[index] = data[j];
            C[q]--;
        }

        // 将排序好的数据拷回
        for (int i = 0; i < n; i++)
        {
            data[i] = tmp[i];
           
        }
        
        // 下一位
        r = r * 10;
    }
    delete []tmp;
}

10、堆及堆排序

堆排序是一种原地的、时间复杂度为 O(nlogn) 的排序算法。

我们知道堆是一种特殊的树,底层实现为数组,那什么样的树才是堆只要满足这两点,它就是一个堆:

  • 堆是一个完全二叉树;
  • 堆中每一个节点的值都必须大于等于(或小于等于)其左右子树节点的值。

对于每个节点的值都大于等于子树中每个节点值的堆,我们叫做“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫做“小顶堆”。

第 1 个和第 2 个是大顶堆,第 3 个是小顶堆,第 4 个不是堆。

对堆我们执行增删改查的过程也叫做堆化,堆化实际上有两种,从下往上和从上往下。堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比,然后交换。

堆的插入

如下,我们将新数据22插入到堆上。此时我们可以让新插入的节点与父节点对比大小。如果不满足子节点小于等于父节点的大小关系,我们就互换两个节点。一直重复这个过程,直到父子节点之间满足节点小于等于父节点的大小。

public class Heap {
  private int[] a; // 数组,从下标1开始存储数据
  private int n;  // 堆可以存储的最大数据个数
  private int count; // 堆中已经存储的数据个数

  public Heap(int capacity) {
    a = new int[capacity + 1];
    n = capacity;
    count = 0;
  }

  public void insert(int data) {
    if (count >= n) return; // 堆满了
    ++count;
    a[count] = data;
    int i = count;
    while (i/2 > 0 && a[i] > a[i/2]) { // 自下往上堆化
      swap(a, i, i/2); // swap()函数作用:交换下标为i和i/2的两个元素
      i = i/2;
    }
  }
 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Chiang木

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

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

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

打赏作者

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

抵扣说明:

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

余额充值