堆排序

数组、链表都是一维的数据结构,相对来说比较容易理解,而堆是二维的数据结构,对抽象思维的要求更高,所以许多程序员「谈堆色变」。但堆又是数据结构进阶必经的一步,我们不妨静下心来,将其梳理清楚。

堆:符合以下两个条件之一的完全二叉树:

  • 根节点的值 ≥ 子节点的值,这样的堆被称之为最大堆,或大顶堆;
  • 根节点的值 ≤ 子节点的值,这样的堆被称之为最小堆,或小顶堆。

为了有一个轻松的开场,我们先来看一个程序员的段子放松一下:

你有哪些用计算机技能解决生活问题的经历?

我认识一个大牛,他不喜欢洗袜子,又不喜欢袜子的臭味。于是他买了很多样式一样的袜子,把这些袜子放在地上,根据臭的程度,摆一个二叉堆。每天早上,他 pop 两只最“香”的袜子,穿上;晚上回到家,把袜子脱下来,push 到堆里。某一天,top 的袜子超过他的耐臭能力,全扔掉,买新的。

如果我们将袜子 「臭的程度」 量化,这位大牛每天做的事情就是构建一个大顶堆,然后将堆顶的袜子取出来。再调整剩下的袜子,构建出一个新的大顶堆,再次取出堆顶的袜子。这个过程使用的就是堆排序的思想,它是由 J. W. J. Williams 在 1964 年发明的。

堆排序过程如下:

  • 用数列构建出一个大顶堆,取出堆顶的数字;

  • 调整剩余的数字,构建出新的大顶堆,再次取出堆顶的数字;

  • 循环往复,完成整个排序。

整体的思路就是这么简单,我们需要解决的问题有两个:

  • 如何用数列构建出一个大顶堆;
  • 取出堆顶的数字后,如何将剩余的数字调整成新的大顶堆。

构建大顶堆 & 调整堆

构建大顶堆有两种方式:

  • 方案一:从 0 开始,将每个数字依次插入堆中,一边插入,一边调整堆的结构,使其满足大顶堆的要求;
  • 方案二:将整个数列的初始状态视作一棵完全二叉树,自底向上调整树的结构,使其满足大顶堆的要求。

方案二更为常用,动图演示如下:

在介绍堆排序具体实现之前,我们先要了解完全二叉树的几个性质。将根节点的下标视为 0,则完全二叉树有如下性质:

  • 对于完全二叉树中的第 i 个数,它的左子节点下标:left = 2i + 1

  • 对于完全二叉树中的第 i 个数,它的右子节点下标:right = left + 1

  • 对于有 n 个元素的完全二叉树(n≥2)(n≥2),它的最后一个非叶子结点的下标:n/2 - 1

堆排序代码如下:

public static void heapSort(int[] arr) {// 构建初始大顶堆buildMaxHeap(arr);for (int i = arr.length - 1; i > 0; i--) {// 将最大值交换到数组最后swap(arr, 0, i);// 调整剩余数组,使其满足大顶堆maxHeapify(arr, 0, i);}
}
// 构建初始大顶堆
private static void buildMaxHeap(int[] arr) {// 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1for (int i = arr.length / 2 - 1; i >= 0; i--) {maxHeapify(arr, i, arr.length);}
}
// 调整大顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小
private static void maxHeapify(int[] arr, int i, int heapSize) {// 左子结点下标int l = 2 * i + 1;// 右子结点下标int r = l + 1;// 记录根结点、左子树结点、右子树结点三者中的最大值下标int largest = i;// 与左子树结点比较if (l < heapSize && arr[l] > arr[largest]) {largest = l;}// 与右子树结点比较if (r < heapSize && arr[r] > arr[largest]) {largest = r;}if (largest != i) {// 将最大值交换为根结点swap(arr, i, largest);// 再次调整交换数字后的大顶堆maxHeapify(arr, largest, heapSize);}
}
private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}

尚硅谷:

package 排序;import java.util.Arrays;import 练习本.a;public class 堆排序 {/*** 1.无序序列构建成一个堆,根据升序降序需求选择大腚堆或小顶堆* 2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端* 3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到这个序列有序* */public static void main(String[] args) {// TODO Auto-generated method stub// 要求将数组进行升序排序int arr[] = {4,6,8,5,9};heapSort(arr);}// 编写一个堆排序的方法public static void heapSort(int arr[]) {int temp = 0;// 分步完成
//      adjustHeap(arr, 1, arr.length);
//      System.err.println("第一次调整"+Arrays.toString(arr));    // 4,9,8,5,6
//      adjustHeap(arr, 0,arr.length);
//      System.err.println("第一次调整"+Arrays.toString(arr));    // 9, 6, 8, 5, 4/*** 1.无序序列构建成一个堆,根据升序降序需求选择大腚堆或小顶堆* */for(int i = arr.length / 2 -1; i >=0; i--) {adjustHeap(arr, i, arr.length);}/***2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端*3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到这个序列有序 * */for (int j = arr.length-1;j>0;j--) {// 交换swap(arr, 0, j);// 调整adjustHeap(arr, 0, j);}System.out.println("数组="+Arrays.toString(arr));}// 将一个数组(二叉树),调整成一个大顶堆/*** 1.无序序列构建成一个堆,根据升序降序需求选择大腚堆或小顶堆* 功能:完成 将 以 i对应的非叶子结点的树调整成大顶堆* 例:  int arr[] =   {4,6,8,5,9};* i = 1,adjustHeap=>{4,9,8,5,6}* i = 0,adjustHeap=>{9,4,8,5,6}*                 =>{9,6,8,5,4}* @param arr 待调整的数组* @param i 表示非叶子结点在数组中索引* @param length 表示对多少个元素继续调整,length是在逐渐减少的* */public static void adjustHeap(int arr[],int i,int length) {int temp = arr[i];   // 先取出当前元素的值,保存在临时变量// 开始调整// 说明:k = 2*i+1 是 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;}else {break;}}// 当for循环结束后,我们已经将以i为父结点的树的最大值,放在了最顶(以i为父结点的局部二叉树)arr[i] = temp;}// 交换函数public static void swap(int arr[],int i,int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}

堆排序的第一步就是构建大顶堆,对应代码中的 buildMaxHeap 函数。我们将数组视作一颗完全二叉树,从它的最后一个非叶子结点开始,调整此结点和其左右子树,使这三个数字构成一个大顶堆。

调整过程由 maxHeapify 函数处理, maxHeapify 函数记录了最大值的下标,根结点和其左右子树结点在经过比较之后,将最大值交换到根结点位置。这样,这三个数字就构成了一个大顶堆。

需要注意的是,如果根结点和左右子树结点任何一个数字发生了交换,则还需要保证调整后的子树仍然是大顶堆,所以子树会执行一个递归的调整过程。

这里的递归比较难理解,我们打个比方:构建大顶堆的过程就是一堆数字比赛谁更大。比赛过程分为初赛、复赛、决赛,每场比赛都是三人参加。但不是所有人都会参加初赛,只有叶子结点和第一批非叶子结点会进行三人组初赛。初赛的冠军站到三人组的根结点位置,然后继续参加后面的复赛。

而有的人生来就在上层,比如李小胖,它出生在数列的第一个位置上,是二叉树的根结点,当其他结点进行初赛、复赛时,它就静静躺在根结点的位置等一场决赛。

当王大强和张壮壮,经历了重重比拼来到了李小胖的左右子树结点位置。他们三个人开始决赛。王大强和张壮壮是靠实打实的实力打上来的,他们已经确认过自己是小组最强。而李小胖之前一直躺在这里等决赛。如果李小胖赢了他们两个,说明李小胖是所有小组里最强的,毋庸置疑,他可以继续坐在冠军宝座。

但李小胖如果输给了其中任何一个人,比如输给了王大强。王大强会和张壮壮对决,选出本次构建大顶堆的冠军。但李小胖能够坐享其成获得第三名吗?生活中或许会有这样的黑幕,但程序不会欺骗我们。李小胖跌落神坛之后,就要从王大强的打拼路线回去,继续向下比较,找到自己真正实力所在的真实位置。这就是 maxHeapify 中会继续递归调用 maxHeapify 的原因。

当构建出大顶堆之后,就要把冠军交换到数列最后,深藏功与名。来到冠军宝座的新人又要和李小胖一样,开始向下比较,找到自己的真实位置,使得剩下的 n−1n - 1 个数字构建成新的大顶堆。这就是 heapSort 方法的 for 循环中,调用 maxHeapify 的原因。

变量 heapSize 用来记录还剩下多少个数字没有排序完成,每当交换了一个堆顶的数字,heapSize 就会减 11。在 maxHeapify 方法中,使用 heapSize 来限制剩下的选手,不要和已经躺在数组最后,当过冠军的人比较,免得被暴揍。

这就是堆排序的思想。学习时我们采用的是最简单的代码实现,在熟练掌握了之后我们就可以加一些小技巧以获得更高的效率。比如我们知道计算机采用二进制来存储数据,数字左移一位表示乘以 22,右移一位表示除以 22。所以堆排序代码中的arr.length / 2 - 1 可以修改为 (arr.length >> 1) - 1,左子结点下标2 * i + 1可以修改为(i << 1) + 1。需要注意的是,位运算符的优先级比加减运算的优先级低,所以必须给位运算过程加上括号。

注:在有的文章中,作者将堆的根节点下标视为 11,这样做的好处是使得第 i 个结点的左子结点下标为 2i,右子结点下标为 2i + 1,与 2i + 12i + 2 相比,计算量会少一点,本文未采取这种实现,但两种实现思路的核心思想都是一致的。

分析可知,堆排序是不稳定的排序算法。

时间复杂度 & 空间复杂度

堆排序分为两个阶段:初始化建堆(buildMaxHeap)和重建堆(maxHeapify,直译为大顶堆化)。所以时间复杂度要从这两个方面分析。

根据数学运算可以推导出初始化建堆的时间复杂度为 O(n),重建堆的时间复杂度为 O(nlog⁡n),所以堆排序总的时间复杂度为 O(nlog⁡n)。推导过程较为复杂,故不再给出证明过程。

堆排序的空间复杂度为 O(1),只需要常数级的临时变量。

堆排序是一个优秀的排序算法,但是在实际应用中,快速排序的性能一般会优于堆排序。

练习

算法题:力扣 215. 数组中的第 K 个最大元素

在选择排序小节,我们提到过,这种只需要部分排序的场景,非常适合用选择排序或堆排序来完成。因为他们的排序过程都是每次找出数组中的最大值(或最小值),依次将每个数字排好序。这两者之间,堆排序在性能上又比选择排序更好。

// 本题的解题思路是,先构建初始大顶堆,然后再将堆调整 k-1 次,此时,堆顶的元素就是第 k 个最大元素。class Solution {public int findKthLargest(int[] nums, int k) {buildMaxHeap(nums);// 调整 k-1 次for (int i = nums.length - 1; i > nums.length - k; i--) {swap(nums, 0, i);maxHeapify(nums, 0, i);}// 此时,堆顶的元素就是第 k 大的数return nums[0];}// 构建初始大顶堆public static void buildMaxHeap(int[] arr) {// 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1for (int i = arr.length / 2 - 1; i >= 0; i--) {maxHeapify(arr, i, arr.length);}}// 调整大顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小private static void maxHeapify(int[] arr, int i, int heapSize) {// 左子结点下标int l = 2 * i + 1;// 右子结点下标int r = l + 1;// 记录根结点、左子树结点、右子树结点三者中的最大值下标int largest = i;// 与左子树结点比较if (l < heapSize && arr[l] > arr[largest]) {largest = l;}// 与右子树结点比较if (r < heapSize && arr[r] > arr[largest]) {largest = r;}if (largest != i) {// 将最大值交换为根结点swap(arr, i, largest);// 再次调整交换数字后的大顶堆maxHeapify(arr, largest, heapSize);}}private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}

java堆排序:

class Solution {public static int findKthLargest(int[] nums, int k) {heapSort(nums);return nums[nums.length-k];}// 编写一个堆排序的方法public static void heapSort(int arr[]) {int temp = 0;// 分步完成
//      adjustHeap(arr, 1, arr.length);
//      System.err.println("第一次调整"+Arrays.toString(arr));    // 4,9,8,5,6
//      adjustHeap(arr, 0,arr.length);
//      System.err.println("第一次调整"+Arrays.toString(arr));    // 9, 6, 8, 5, 4/*** 1.无序序列构建成一个堆,根据升序降序需求选择大腚堆或小顶堆* */// 构造一个大顶堆for(int i = arr.length / 2 -1; i >=0; i--) {adjustHeap(arr, i, arr.length);}/***2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端*3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到这个序列有序 * */for (int j = arr.length-1;j>0;j--) {// 交换swap(arr, 0, j);// 调整adjustHeap(arr, 0, j);}
//      System.out.println("数组="+Arrays.toString(arr));}// 将一个数组(二叉树),调整成一个大顶堆public static void adjustHeap(int arr[],int i,int length) {int temp = arr[i]; // 先取出当前元素的值,保存在临时变量// 开始调整// 说明:k = 2*i+1 是 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;}else {break;}}// 当for循环结束后,我们已经将以i为父结点的树的最大值,放在了最顶(以i为父结点的局部二叉树)arr[i] = temp;}// 交换函数public static void swap(int arr[],int i,int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}

go堆排序:

func findKthLargest(nums []int, k int) int {HeapSort(nums)return nums[len(nums)-k]
}// 编写一个堆排序的方法
func HeapSort(arr []int) []int {/*** 1.无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆* */// 构造一个大顶堆for i := len(arr)/2-1 ; i>=0;i--{adjustHeap(arr,i,len(arr))}/***2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端*3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到这个序列有序* */for j := len(arr)-1;j>0;j-- {arr[0],arr[j] = arr[j],arr[0]adjustHeap(arr,0,j)}return arr
}// 将一个数组(二叉树),调整成一个大顶堆
func adjustHeap(arr []int,i int, length int)  {temp := arr[i];     // // 先取出当前元素的值,保存在临时变量// 开始调整// 说明:k = 2*i+1 是 i 的左子结点for k := 2*i+1;k < length; k = 2*k+1 {if k+1< length && arr[k] < arr[k+1] {k++;}if arr[k] > temp {arr[i] = arr[k];i = k;}else {break}}// 当for循环结束后,我们已经将以i为父结点的树的最大值,放在了最顶(以i为父结点的局部二叉树)arr[i] = temp
}

算法题:剑指 Offer 40. 最小的 k 个数

// 本题与上一题是类似的。
// 使用堆排序,先构建初始小顶堆,然后调整 k 次。此时数组末尾的 k 个元素组成的数组就是答案。
class Solution {public int[] getLeastNumbers(int[] arr, int k) {buildMinHeap(arr);// 调整 k 次for (int i = arr.length - 1; i > arr.length - k - 1; i--) {swap(arr, 0, i);minHeapify(arr, 0, i);}// 取出 arr 末尾的 k 个元素int[] result = new int[k];System.arraycopy(arr, arr.length - k, result, 0, k);return result;}// 构建初始小顶堆private static void buildMinHeap(int[] arr) {// 从最后一个非叶子结点开始调整小顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1for (int i = arr.length / 2 - 1; i >= 0; i--) {minHeapify(arr, i, arr.length);}}// 调整小顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小private static void minHeapify(int[] arr, int i, int heapSize) {// 左子结点下标int l = 2 * i + 1;// 右子结点下标int r = l + 1;// 记录根结点、左子树结点、右子树结点三者中的最小值下标int smallest = i;// 与左子树结点比较if (l < heapSize && arr[l] < arr[smallest]) {smallest = l;}// 与右子树结点比较if (r < heapSize && arr[r] < arr[smallest]) {smallest = r;}if (smallest != i) {// 将最小值交换为根结点swap(arr, i, smallest);// 再次调整交换数字后的小顶堆minHeapify(arr, smallest, heapSize);}}private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}

go堆排序:

func getLeastNumbers(arr []int, k int) []int {HeapSort(arr)a := make([]int,0,k)for i :=0 ; i<k ; i++ {a = append(a, arr[i])}return a
}// 编写一个堆排序的方法
func HeapSort(arr []int) []int {/*** 1.无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆* */// 构造一个大顶堆for i := len(arr)/2-1 ; i>=0;i--{adjustHeap(arr,i,len(arr))}/***2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端*3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到这个序列有序* */for j := len(arr)-1;j>0;j-- {arr[0],arr[j] = arr[j],arr[0]adjustHeap(arr,0,j)}return arr
}// 将一个数组(二叉树),调整成一个大顶堆
func adjustHeap(arr []int,i int, length int)  {temp := arr[i];     // // 先取出当前元素的值,保存在临时变量// 开始调整// 说明:k = 2*i+1 是 i 的左子结点for k := 2*i+1;k < length; k = 2*k+1 {if k+1< length && arr[k] < arr[k+1] {k++;}if arr[k] > temp {arr[i] = arr[k];i = k;}else {break}}// 当for循环结束后,我们已经将以i为父结点的树的最大值,放在了最顶(以i为父结点的局部二叉树)arr[i] = temp
}

Java堆排序:

class Solution {public static int[] getLeastNumbers(int[] arr, int k) {heapSort(arr);int a[] = new int [k];for (int i = 0; i < k; i++) {a[i] = arr[i];}return a;}// 编写一个堆排序的方法public static void heapSort(int arr[]) {int temp = 0;// 分步完成
//      adjustHeap(arr, 1, arr.length);
//      System.err.println("第一次调整"+Arrays.toString(arr));    // 4,9,8,5,6
//      adjustHeap(arr, 0,arr.length);
//      System.err.println("第一次调整"+Arrays.toString(arr));    // 9, 6, 8, 5, 4/*** 1.无序序列构建成一个堆,根据升序降序需求选择大腚堆或小顶堆* */// 构造一个大顶堆for(int i = arr.length / 2 -1; i >=0; i--) {adjustHeap(arr, i, arr.length);}/***2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端*3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到这个序列有序 * */for (int j = arr.length-1;j>0;j--) {// 交换swap(arr, 0, j);// 调整adjustHeap(arr, 0, j);}
//      System.out.println("数组="+Arrays.toString(arr));}// 将一个数组(二叉树),调整成一个大顶堆/*** 1.无序序列构建成一个堆,根据升序降序需求选择大腚堆或小顶堆* 功能:完成 将 以 i对应的非叶子结点的树调整成大顶堆* 例:  int arr[] =   {4,6,8,5,9};* i = 1,adjustHeap=>{4,9,8,5,6}* i = 0,adjustHeap=>{9,4,8,5,6}*                 =>{9,6,8,5,4}* @param arr 待调整的数组* @param i 表示非叶子结点在数组中索引* @param length 表示对多少个元素继续调整,length是在逐渐减少的* */public static void adjustHeap(int arr[],int i,int length) {int temp = arr[i];    // 先取出当前元素的值,保存在临时变量// 开始调整// 说明:k = 2*i+1 是 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;}else {break;}}// 当for循环结束后,我们已经将以i为父结点的树的最大值,放在了最顶(以i为父结点的局部二叉树)arr[i] = temp;}// 交换函数public static void swap(int arr[],int i,int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}


答疑资料qq群:1007576722

【算法基础】堆排序——O(nlogn)相关推荐

  1. 算法基础、算法比赛快速入门(java)

    想用Java快速入门算法?这篇文章你得看! 提示:本文章适合想要入门算法,并且想 "快速" 达到一定成果的同学们阅读~ 文章非常非常非常长(可能是你见过最长的算法基础篇章)!!! ...

  2. 算法基础知识总结(基础算法)

    算法基础知识总结 Efficient procedures for solving problems on large inputs. 一.基础算法 1.快速排序 1.类别:快速排序是一种 交换排序, ...

  3. 算法基础(ACWing)

    算法基础 基础算法 快速排序 快速排序,背一背板子. 一点心得: 快速排序不是稳定算法 快速排序时间复杂度O(nlogn),空间复杂度是O(longn)虽然没有开辟新的空间但是递归占用了栈空间. 主要 ...

  4. 算法基础-十大排序算法及其优化(文末有抽奖福利哦)

    算法基础-十大排序算法及其优化 算法基础-十大排序算法及其优化 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kw1LA5Q4-1607527572080)(/uplo ...

  5. 十大排序算法之堆排序

    十大排序算法之堆排序 本文采用Java书写选择排序,其他语言类似可以借鉴着写 思想:所谓堆排序就是通过构造最大堆(升序)或者最小堆(降序)来进行排列的方法.可能有些童鞋不知道何为最大堆,何为最小堆.这 ...

  6. 排序算法-06堆排序(python实现)

    堆排序 简介 经典的选择排序,实现利用了二叉树这样的数据结构. 算法实现 预备知识 堆是一棵顺序存储的完全二叉树.(若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个 ...

  7. 数据结构与算法之堆排序

    数据结构与算法之堆排序 目录 堆排序介绍 代码实现 1. 堆排序介绍 堆排序(Heapsort)是指利用堆((英语:heap)是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组 ...

  8. 排序算法之---堆排序(很重要的一个结构,新手入门必备)

    排序算法之---堆排序(很重要的一个结构,新手入门必备) 先来简单的介绍一下堆结构: 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlo ...

  9. 数据结构和算法基础(4)——树

    一.概念 1. 为什么需要树这种数据结构 ① 数组存储方式 优点:通过下标方式访问元素,速度快.对于有序数组可以通过二分查找提高检测速度. 缺点:如果要通过内容来查找元素的位置,或者插入删除值时,效率 ...

  10. 数据结构与算法基础-青岛大学-王卓

    数据结构与算法基础(青岛大学-王卓)_哔哩哔哩_bilibili 文章目录: 第一章:数据结构的基本概念 1.逻辑结构的种类 2.存储结构的种类 ​3.抽象数据类型的形式定义 4.Complex抽象书 ...

最新文章

  1. ibatis 批量插入
  2. sklearn识别单张自定义手写数字图片
  3. Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)
  4. 什么是框架?spring mvc框架
  5. c语言甘勇第二版第五章答案,C语言(1) - Patata的个人页面 - OSCHINA - 中文开源技术交流社区...
  6. 节点主动可信监控机制
  7. 关于企业应用SAP成本管理模式与方法的一些思考
  8. nyoj Color the fence
  9. opencv2,3 ,4几个版本的区别及冲突性,使用的时候用头文件注意
  10. Java基础知识总结(2022最新版一)|CSDN创作打卡
  11. weka mac安装
  12. Autoware安装使用教程
  13. ASP.Net Core 发布在IIS部署出现502.5错误的解决办法
  14. debian adsl上网
  15. matlab实现拍照功能,matlab下拍照功能的实现
  16. flex 4 dropdownlist skin自定义 1
  17. jsp做的留言系统(防止非法登录、增删改查留言)
  18. 彻底搞懂BLDC与PMSM的区别
  19. linux c查看子进程,Linux 如何查看进程和控制进程
  20. Y05 - 017、猜小埋年龄游戏

热门文章

  1. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java装修服务分析系统03563
  2. [转载]手机号码IP地址归属地查询
  3. 申请注册@MSN.COM邮箱!
  4. 图像高斯金字塔(三层简单举例matlab)
  5. web前端面试合集2022版
  6. 使用宏批量修改word中图片大小
  7. Android6下的视频播放器,MePlayer视频播放器
  8. 大数据课程——Storm综合应用
  9. 契约测试(上):什么是契约测试
  10. 使用Protege构建知识图谱