2.常见8种排序算法分析笔记之-时间O(NlogN)三种(归并、快排、堆排序)

2023-05-16

文章目录

  • 时间复杂度为 O ( N l o g N ) O(Nlog_{}N) O(NlogN)的三种排序法
    • 一、归并排序法
      • 1.代码实现思想
      • 2.复杂度分析
      • 3.代码实现
      • 4.代码测试
    • 二、快速排序法
      • 1.代码实现思想
      • 2.复杂度分析
      • 3.代码实现
        • 单路快速排序法:
        • 双路快速排序法:
        • 三路快速排序法:
      • 4.代码测试
    • 三、堆排序法
      • 1.代码实现思想
      • 2.代码实现
        • 准备工作:两种堆化操作
        • 排序算法实现
      • 3.复制度分析
      • 4.代码测试

时间复杂度为 O ( N l o g N ) O(Nlog_{}N) O(NlogN)的三种排序法

参考文章

慕课网数据结构与算法课程
归并排序法

一、归并排序法

1.代码实现思想

  • 使用归并思想,也是分治思想,先走向递归深处,即分的过程,直到递归终止条件为止

  • 对于递归终止条件一般为只有一个叶子节点,为了优化性能,对于小数据采用插入排序法

    • 小数据用插入原因是,深度已经不是主要影响因素,新建数组,两两比较复杂度会高于插入过程
    • 注意,一定不要忘记return,否则递归永远无法终止
  • 分的方案是二分法,每次找区间的中间值,注意数据越界情况,然后将分化的两个区间继续二分

  • 合的方案,是新建一个数组,将要合并的两个数组逐个比较,将较小元素依次放入新数组中,最后将新数组copy到原数组中进行数值覆盖,注意,数组索引的对应问题

    • 要先定义好指针变量,分别指向要合并的两个区间起始位置,以及新数组的起始位置
    • 如果两个区间其中一个已经遍历完了,要先进行判断处理,防止指针越界,偏离正确逻辑,产生错误结果
  • 为了优化性能,在递归前,就将temp新数组容器准备好,依次传入即可,以免每次递归时新建开销

    • 合并前,也先判断要合并的两个区间是否左区间已经完全小于右区间,已经排好序的可以不用调用合并过程

2.复杂度分析

  • 时间复杂度: O ( N l o g N ) O(Nlog_{}N) O(NlogN),即需要 O ( l o g N ) O(log_{}N) O(logN)层深度,每层 O ( N ) O(N) O(N)的操作
    • 若为有序数组,复杂度最好为 O ( l o g N ) O(log_{}N) O(logN),只需遍历 O ( l o g N ) O(log_{}N) O(logN)层,每层均无需合并操作,为 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( N ) O(N) O(N),即需要开辟新的长度为 O ( N ) O(N) O(N)的新数组空间
  • 稳定性:指的是相同元素保存相对位置不变的特性,在合并逻辑中,左右子数组相等时,优先排左数组元素,可以保证稳定性
    在这里插入图片描述

3.代码实现

import java.util.Arrays;

import bubbleSort.ArrayGenerator;
import bubbleSort.SortingHelper;
import insertionSort.InsertionSort;

/**
 * 
 * @author LiuS
 * 归并排序多用于对象排序,可以稳定排序
 * java库中,TimSort是改进的MergeSort,其中TimSort是直接分成n块,然后两两归并
 * 			小于某值,用二分插入排序
 * 小数据用插入原因是,深度已经不是主要影响因素,新建数组,两两比较复杂度会高于插入过程
 */
public class MergeSort {
	private MergeSort() {}
	private static final int MIN_MERGE = 32;
	
	public static <E extends Comparable<E>> void sort(E[] arr) {
		if(arr == null || arr.length <= 2) return;
		
		E[] temp = Arrays.copyOf(arr, arr.length); // 开辟和额外空间,减少每次递归重新开辟空间的消耗
		sort(arr, 0, arr.length - 1, temp);
	}

	private static <E extends Comparable<E>> void sort(
			E[] arr, int left, int right, E[] temp) {
		// 递归终止条件,小于某一值,用插入法优化
		if((right - left) <= MIN_MERGE) {
			InsertionSort.sort(arr, left, right);
			return;  // 千万别忘记返回终止
		}
//		if(left >= right) return;
		
		int mid = left + (right - left) / 2;
		
		sort(arr, left, mid, temp);
		sort(arr, mid + 1, right, temp);
		
		if(arr[mid].compareTo(arr[mid + 1]) > 0)
			merge(arr, left, mid, right, temp);	
	}

	private static <E extends Comparable<E>> void merge(E[] arr, int left, int mid, int right, E[] temp) {
		int i = left; // 左序列指针
		int j = mid + 1; // 右序列指针
		int t = left; // 新数组指针
		
		while(t <= right) { // 一定要找好数组的索引位置
			if(i > mid) {
				temp[t ++] = arr[j ++];
			} else if(j > right) {
				temp[t ++] = arr[i ++];
			} else if(arr[i].compareTo(arr[j]) <= 0) {
				temp[t ++] = arr[i ++];
			} else if(arr[i].compareTo(arr[j]) > 0) {
				temp[t ++] = arr[j ++];
			}
		}
		
//		将temp数组元素copy到arr数组中,注意索引位置一一对应
//		temp = Arrays.copyOfRange(arr, left, right); // 此方法索引还是从0开始
		System.arraycopy(temp, left, arr, left, right - left + 1);
		
//		int t = 0;
//		while(left <= right){
//            arr[left++] = temp[t++]; // arr[left++] = temp[left++];不对,left每次加2了
//        }
	}
}

4.代码测试

public static void main(String[] args) {
		int n = 1000000;
		Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
		SortingHelper.sortTime("MergeSort", arr);
	}
MergeSort, n = 1000000 : 0.490878 s
快速排序法

二、快速排序法

1.代码实现思想

  • 同样也是分治递归思想,先以分界点将区间分为两个区域,即小于分界点元素的值域和大于分界点的值域,分别在分界点左右两边

  • 对于区分左右区间并找到分界点的partition过程,主要是双指针法,分别定义两个指针变量指向小于分界点和大于分界点的数,并依次移动遍历,并返回分界点位置,以供下一层递归调用

  • 递归终止条件是区间元素只有一个,即上一个分界点的左右区间只有一个元素,由于分别小于和大于分界点,故一定可以与分界点构成有序数列

  • 性能优化

    • 分的结果类似二分法,能左右均分,避免一边倒的现象分界点的数值选取处理为目标区域的随机数,不能固定
    • 如果有大量相同元素单路排序,使用快慢指针,均从左边开始遍历,还是很容易退化成一边倒的现象,故使用双路快速排序法,即partition过程,改为左右指针从两边向中间遍历,遇到相等元素也交换,使得相等元素均分在左右两侧
    • 对于大量相同元素性能可以优化为 O ( N ) O(N) O(N),使用三路排序法,因为,三路排序的逻辑是将区间分为三个区域,等于分界点的元素单独划分一个区域,若大量相同元素,另外两个区域的元素会很少甚至为空,这样就减少递归层次,只需完成partition过程即可
  • 单路快速排序法图解:

在这里插入图片描述

  • 双路快速排序法图解:

在这里插入图片描述
在这里插入图片描述

2.复杂度分析

  • 时间复杂度: O ( N l o g N ) O(Nlog_{}N) O(NlogN),即需要 O ( l o g N ) O(log_{}N) O(logN)层深度,每层 O ( N ) O(N) O(N)的操作
    • 若为相同数组,三路快速排序法复杂度最好为 O ( N ) O(N) O(N),只需遍历partition过程,左右区域为空无需递归
  • 空间复杂度: O ( l o g N ) O(log_{}N) O(logN),即每次partition过程需要额外的栈空间
  • 稳定性:指的是相同元素保存相对位置不变的特性,由于数值会跳跃,难以保证稳定性

3.代码实现

单路快速排序法:

import java.util.Random;

import bubbleSort.ArrayGenerator;
import bubbleSort.SortingHelper;

public class QuickSort {
	private QuickSort() {}
	
	/**
	 * 
	 * @param <E> 支持泛型
	 * @param arr 传入数组
	 *  单路快速排序算法
	 */
	public static <E extends Comparable<E>> void sort1Way(E[] arr) {
		if(arr == null || arr.length <= 2) return;
		
		Random rad = new Random();
		sort1Way(arr, 0, arr.length - 1, rad);
	}
	
	private static <E extends Comparable<E>> void sort1Way(
			E[] arr, int left, int right, Random rad) {
		if(left >= right) return;
		
		int p = partition(arr, left, right, rad);
		
		sort1Way(arr, left, p - 1, rad);
		sort1Way(arr, p + 1, right, rad);
	}

	/**
	  * 分治过程,即将目标数组按照分界点区分左右大小区域
	 * @param <E>
	 * @param arr, 要处理区间数组
	 * @param left, 左边界
	 * @param right, 右边界
	 * @param rad,传入的随机数辅助对象
	 * @return, 返回分界点索引位置
	 */
	private static <E extends Comparable<E>> int partition(
        E[] arr, int left, int right, Random rad) {
		int radIndex = left + rad.nextInt(right - left + 1); // 找到随机一个索引
		swap(arr, left, radIndex); // 将随机索引指向的值作为分界点,放在第一个位置
		
		E v = arr[left];   	// 定义好分界点
		int i = left + 1;  	// 快指针,遍历数组,遇到小于v的数交换,i - 1 均指向>=v
		int j = left;		// 慢指针,指向<v的数,从i指向的数中交换所得
		
		for(; i <= right; i ++) {
			if(arr[i].compareTo(v) < 0) {
				j ++; // 慢指针先开辟空间
				swap(arr, i, j); // 新开辟的地址储存<v的数,i交换后依旧指向>=v的数
			}
		}
		
		swap(arr, j, left); // 将最后一个j指向的小于v的数与v交换,v左边全为小于v的数
		
		return j;			// 交换后,j指向的就是v的值,即分界点
	}

	private static <E> void swap(E[] arr, int i, int j) {
		E t = arr[i];
		arr[i] = arr[j];
		arr[j] = t;
	}
    
	public static void main(String[] args) {
		int[] nums = {100000, 1000000};
		for(int n : nums) {
			Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
			SortingHelper.sortTime("QuickSort1Way", arr);
		}
	}
}
QuickSort1Way, n = 100000 : 0.039567 s
QuickSort1Way, n = 1000000 : 0.359868 s // 大致相差10倍

双路快速排序法:

import java.util.Random;

import bubbleSort.ArrayGenerator;
import bubbleSort.SortingHelper;

public class QuickSort {
	private QuickSort() {}	
	/**
	 * 
	 * @param <E> 支持泛型
	 * @param arr 传入数组
	  *  双路排序算法
	 */
	public static <E extends Comparable<E>> void sort2Way(E[] arr) {
		if(arr == null || arr.length <= 2) return;
		
		Random rad = new Random();
		sort2Way(arr, 0, arr.length - 1, rad);
	}
	
	private static <E extends Comparable<E>> void sort2Way(
			E[] arr, int left, int right, Random rad) {
		if(left >= right) return;
		
		int p = partition2(arr, left, right, rad);
		
		sort2Way(arr, left, p - 1, rad);
		sort2Way(arr, p + 1, right, rad);
	}

	/**
	  * 分治过程,即将目标数组按照分界点区分左右大小区域
	 * @param <E>
	 * @param arr, 要处理区间数组
	 * @param left, 左边界
	 * @param right, 右边界
	 * @param rad,传入的随机数辅助对象
	 * @return, 返回分界点索引位置
	 */
	private static <E extends Comparable<E>> int partition2(E[] arr, int left, int right, Random rad) {
		int radIndex = left + rad.nextInt(right - left + 1);
		swap(arr, left, radIndex);
		
		E v = arr[left];	// 分界值
		int i = left + 1;	// 指向左区域指针
		int j = right;		// 指向右区域指针
		
		while(true) {		
			// 保证i-1指针指向的数一定小于v,大于等于v,停止移动,等待处理
			while(i <= j && arr[i].compareTo(v) < 0) { 
				i ++;		// i==j,继续循环,表明最后一个位置j指向的数小于v
			}
			// 保证j+1指针指向的数一定大于v,小于等于v,停止移动,等待处理
			while(i <= j && arr[j].compareTo(v) > 0) { 
				j --;		// i==j时继续循环,目的是让j--,使得最后j指向的数小于v,与v交换
			}
			
			if(i >= j) break;	// 如果指针越界,就要立即终止循环,不能再走,否则j指向的位置就不对了
								// i=j能走到这一步,说明i,j均指向v,此时j就是分界点,可以结束循环了
			if(arr[i].compareTo(arr[j]) != 0) {	// 如果均等于v,可以直接跳过,交换没有意义
				swap(arr, i , j);// i指向的值大于v时或j指向的值小于v时,交换,放入正确区间
			}
			// 交换后,继续遍历,一边等于v,也交换,这样等于v的数均分在两侧
			// 处理完相等的元素或要交换的元素后,指针需要继续走,判断下个元素
			i ++;
			j --;
		}
		
		swap(arr, j, left);
		return j;
	}
	
	public static void main(String[] args) {
		int[] nums = {100000, 1000000};
		for(int n : nums) {
			Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
			Integer[] arr2 = Arrays.copyOf(arr, arr.length);
			
			SortingHelper.sortTime("QuickSort1Way", arr);
			SortingHelper.sortTime("QuickSort2Way", arr2);
		}
	}
}
QuickSort1Way, n = 100000 : 0.040037 s
QuickSort2Way, n = 100000 : 0.035355 s
QuickSort1Way, n = 1000000 : 0.352041 s
QuickSort2Way, n = 1000000 : 0.235757 s		// 双路快排性能更优

三路快速排序法:

	// 三路快排
	public static <E extends Comparable<E>> void sort3Way(E[] arr) {
		Random rad = new Random();
		sort3Way(arr, 0, arr.length - 1, rad);
	}

	private static <E extends Comparable<E>> void sort3Way(
			E[] arr, int left, int right, Random rad) {
		if (left >= right)
			return;

		int radIndex = left + rad.nextInt(right - left + 1);
		swap(arr, left, radIndex);

		// arr[l + 1,lt]<v,arr[lt+1,i-1]==v,arr[gt,r]>v
		// 初始化,使得三个区间都为空区间
		int lt = left;		// lt为指向左区域指针
		int i = left + 1; 	// i为遍历指针,i-1指向等于v区域
		int gt = right + 1; // gt为指向右区域指针
		while (i < gt) {	// 不是所有的i都加加,用while循环,循环终止i==gt
			if (arr[i].compareTo(arr[left]) < 0) {
				lt++;		// 开辟新空间
				swap(arr, i, lt);
				i++;
			} else if (arr[i].compareTo(arr[left]) > 0) {
				gt--;
				swap(arr, i, gt);
			} else {
				i++;	// i指向数值等于v,不做任何操作
			}
		}

		swap(arr, left, lt);	// 保证lt指向的数等于v
		// 处理之后,arr[l,lt -1]<v,arr[lt,i-1]==v,arr[gt,r]>v
		// 递归处理< v and > v 的部分就行
		sort3Way(arr, left, lt - 1, rad);
		sort3Way(arr, gt, right, rad);
	}

4.代码测试

public static void main(String[] args) {
		int[] nums = {100000, 1000000};
		for(int n : nums) {
			System.out.println("Random Array:");
			Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
			Integer[] arr2 = Arrays.copyOf(arr, arr.length);
			Integer[] arr3 = Arrays.copyOf(arr, arr.length);		
			SortingHelper.sortTime("QuickSort1Way", arr);
			SortingHelper.sortTime("QuickSort2Way", arr2);
			SortingHelper.sortTime("QuickSort3Way", arr3);
			System.out.println();
			
			System.out.println("Order Array:");
			arr = ArrayGenerator.generateOrderedArray(n);
			arr2 = Arrays.copyOf(arr, arr.length);
			arr3 = Arrays.copyOf(arr, arr.length);
			SortingHelper.sortTime("QuickSort1Way", arr);
			SortingHelper.sortTime("QuickSort2Way", arr2);
			SortingHelper.sortTime("QuickSort3Way", arr3);
			System.out.println();
	
			System.out.println("Same Value Array:");
			arr = ArrayGenerator.generateRandomArray(n, 1);
			arr2 = Arrays.copyOf(arr, arr.length);
			arr3 = Arrays.copyOf(arr, arr.length);
			//SortingHelper.sortTime("QuickSort1Way", arr);
			SortingHelper.sortTime("QuickSort2Way", arr2);
			SortingHelper.sortTime("QuickSort3Way", arr3);
			System.out.println();
		}
	}
Random Array:
QuickSort1Way, n = 100000 : 0.039846 s
QuickSort2Way, n = 100000 : 0.036051 s
QuickSort3Way, n = 100000 : 0.059368 s

Order Array:
QuickSort1Way, n = 100000 : 0.009889 s
QuickSort2Way, n = 100000 : 0.005750 s
QuickSort3Way, n = 100000 : 0.049616 s

Same Value Array:
QuickSort2Way, n = 100000 : 0.005404 s
QuickSort3Way, n = 100000 : 0.000580 s

Random Array:
QuickSort1Way, n = 1000000 : 0.377061 s
QuickSort2Way, n = 1000000 : 0.243519 s // 双路快速排序法综合性能有优势
QuickSort3Way, n = 1000000 : 0.520272 s

Order Array:
QuickSort1Way, n = 1000000 : 0.217499 s
QuickSort2Way, n = 1000000 : 0.060744 s // 双路快速排序法综合性能有优势
QuickSort3Way, n = 1000000 : 0.379908 s

Same Value Array:
QuickSort2Way, n = 1000000 : 0.068403 s
QuickSort3Way, n = 1000000 : 0.001912 s // 三路快速排序法性能有优势

  • 结果显示,时间复杂度一般为 O ( l o g N ) O(log_{}N) O(logN)
    • 当数组元素不相同时,双路快速排序法综合性能有优势
    • 当数组元素相同或大量相同时,三路快速排序法性能有优势
堆排序法

三、堆排序法

参考文章1,参考文章2

1.代码实现思想

  • 1.首先将无序的序列进行堆化,构建成一个最大堆或最小堆,本文以最大堆为例

    • 可以借助额外空间逐个添加,然后自下而上堆化
    • 可以使用原地堆化,即自上而下逐个对所有非叶子节点进行循环堆化
  • 2.进行排序,因为堆顶元素是最大,将堆顶元素与堆中最后一个元素交换,将最大元素"沉"到数组末端,堆中元素总数减一

    • 流程是;我们把下标为 n 的元素放到堆顶,然后再通过堆化的方法,将剩下的 n−1 个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是 n−1 的位置,一直重复这个过程,直到最后堆中只剩下标为 1 的一个元素,排序工作就完成了。
  • 3.新的数据结构堆化调整,同样采用自上而下堆化处理,因为涉及到堆中元素总数变化,函数设计中要传入size大小的参数,并注意新的堆的大小的维护

  • 扩展:堆的结构,是一颗完全二叉树完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。

2.代码实现

准备工作:两种堆化操作

  • 向最大堆中新添加元素图解:

  • 核心代码实现:

...
private Array<E> data;		// 自定义动态数组
public void add(E e) {
		data.addLast(e);	// 因为调用的是辅助类动态数组,size已经在Array中维护了
		siftUp(data.getSize() - 1);		// 自下往上heapify堆化
}

private void siftUp(int k) {
    // 往上heapify过程,注意k不能为根节点
    while(k > 0 && data[k].compareTo(data[(k - 1) / 2]) > 0) {	// (k-1)/2 父亲节点
        data.swap(k, (k - 1) / 2);	// swap交换函数在自定义数组中已经定义好
        k = (k - 1) / 2;	// 再次往上进行heapify
    }
}

  • 向最大堆中删除元素图解:
  • 核心代码实现:
...
/**
* 自上向下堆化实现
* @param <E>
* @param data ,最大堆结构
* @param k, 向下堆化的起始根节点
* @param n, 最大堆容器的大小
*/
private static <E extends Comparable <E>>void siftDown(E[] data, int k, int n) {
    // n是新堆结构的大小,不能用data.length,data数组中一部分排好序的数不参与堆化
    while(2 * k + 1 < n) {	 
        int j = 2 * k + 1;	// 左子节点索引
        if(j + 1 < n && data[j + 1].compareTo(data[j]) > 0) { // 判断左右子节点哪个大
            j ++;	// 如果右子节点大,j表示右节点
        }
        if(data[k].compareTo(data[j]) < 0) {
            data.swap(k, j);	// 以k为根节点的子二叉树,根节点最大
        }
        k = j;	// 继续往下堆化,将改动的子节点作为根节点循环进行,另一个子节点完整不变
    }
}

排序算法实现

  • 排序图解:
  • 排序算法实现:
public class HeapSort {
	private HeapSort() {};
	
	public static <E extends Comparable<E>> void sort(E[] arr) {
		if(arr == null || arr.length <= 2) return;
		
		// 第一步,原地堆化
		// 找非叶子节点,自下往上找;找到所有非叶子节点后,再自上往下交换进行堆化
		// i为所有非叶子节点,故如果i为右节点,下一步要处理以左节点为根节点的树
		// 即所有非叶子节点排列规律是从右往左,从下往上,层Z字行排列
		for(int i = (arr.length - 2) / 2; i >= 0; i --) { 
			siftDown(arr, i, arr.length);
		}
		
		// 排序
		for(int i = arr.length - 1; i > 0; i --) {
			swap(arr, 0, i);	// 先交换,将堆顶元素放到指定位置i,[i,n)排好序
			siftDown(arr, 0, i);	// 对新的大小的堆结构进行堆化,[i,n)数不参与
		}
	}
	
	/**
	* 自上向下堆化实现
	* @param <E>
	* @param arr ,最大堆结构
	* @param k, 向下堆化的起始根节点
	* @param n, 最大堆容器的大小
	*/
	private static <E extends Comparable <E>>void siftDown(E[] arr, int k, int n) {
	    // n是新堆结构的大小,不能用data.length,data数组中一部分排好序的数不参与堆化
	    while(2 * k + 1 < n) {	 
	        int j = 2 * k + 1;	// 左子节点索引
            // 判断左右子节点哪个大
	        if(j + 1 < n && arr[j + 1].compareTo(arr[j]) > 0) { 
	            j ++;	// 如果右子节点大,j表示右节点
	        }
	        if(arr[k].compareTo(arr[j]) < 0) {
	            swap(arr, k, j);	// 以k为根节点的子二叉树,根节点最大
	        }
            // 继续往下堆化,将改动的子节点作为根节点循环进行,另一个子节点完整不变
	        k = j;	
	    }
	}

	private static <E>void swap(E[] data, int k, int j) {
		E t = data[k];
		data[k] = data[j];
		data[j] = t;		
	}
}

3.复制度分析

  • 时间复杂度: O ( N l o g N ) O(Nlog_{}N) O(NlogN),堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是 O ( n ) O(n) O(n),排序过程的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),所以,堆排序整体的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( 1 ) O(1) O(1),即每次都只需要极个别临时存储空间,原地排序算法
  • 稳定性:指的是相同元素保存相对位置不变的特性,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。故是不稳定的

4.代码测试

public static void main(String[] args) {
		int n = 1000000;
		Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
		Integer[] arr1 = Arrays.copyOf(arr, arr.length);
		Integer[] arr2 = Arrays.copyOf(arr, arr.length);

		SortingHelper.sortTime("MergeSort", arr);
		SortingHelper.sortTime("QuickSort2Way", arr1);
		SortingHelper.sortTime("HeapSort", arr2);
	}
MergeSort, n = 1000000 : 0.424197 s
QuickSort2Way, n = 1000000 : 0.331031 s
HeapSort, n = 1000000 : 0.689467 s
  • 小结:为什么快速排序要比堆排序性能好?

    • 原因1:堆排序数据访问的方式没有快速排序友好。对于快速排序来说,数据是顺序访问的。而对于堆排序来说,数据是跳着访问的。这样对 CPU 缓存是不友好的
    • 原因2:对于同样的数据,在排序过程中,堆排序算法的数据交换次数要多于快速排序。快速排序数据交换的次数不会比逆序度多。但是堆排序的第一步是建堆,建堆的过程会打乱数据原有的相对先后顺序,导致原数据的有序度降低。比如,对于一组已经有序的数据来说,经过建堆之后,数据反而变得更无序了。
  • 堆排序优势主要应用于topK问题

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2.常见8种排序算法分析笔记之-时间O(NlogN)三种(归并、快排、堆排序) 的相关文章

  • BOCHS问题总结篇

    在官网上下载的bochs 2 4 5 win32版 bochs启动时会读bochsrc bxrc里的配置 xff0c 而bochsrc sample txt则是个sample xff0c 可以在这个sample里阅读相关参数的设置 1 RO
  • 关于Access的左连接

    这篇随笔没有什么深奥的技术要讨论 xff0c 只是自己一个知识上的盲点 xff1a 不知道在Access中如何进行左连接的操作 通过在网上搜索 xff0c 最后在CSDN上找到了自己要的答案 xff0c 因此觉得有必要记录下来 xff1a
  • ubuntu下安装Calibre

    Calibre是电子书管理软件 xff0c 支持Amazon Apple Bookeen Ectaco Endless Ideas Google HTC Hanlin Song设备及格式 xff0c 功能十分强大 ubuntu 有很多包都可
  • 编译Linux内核数

    本文是参考了网上多篇帖子而写的算不上什么原创 唯一值得欣慰的只不过在本机上实现罢了 因为毕竟失败了几次 也因为本人是初学驱动编程 很多简单的问题在我来说是相当的困难的 望有识之士不要笑话 最后 xff0c 希望本文能给刚学驱动而还没开头的人
  • 构造内核源码树

    编写驱动程序时 xff0c 需要内核源码树的支持 内核源码树时从内核源代码编译得到的 下面开始构造内核源代码的步骤 以Ubuntu为例子 1 下载内源代码 xff0c 位置www kernel org 注意 xff1a 源码树内核的版本要和
  • 裁剪图像中感兴趣区域python

    题外话 xff1a 比较全面的缩略图及相应源码 http matplotlib org gallery html http www cnblogs com wei li archive 2012 05 23 2506940 html 题外外
  • Linux设备驱动程序(LDD)中snull的编译问题

    对LDD中snull程序 xff0c 编译的时候会有许多问题 xff0c 鉴于网上还没有合适的解决办法 xff0c 做此总结 xff0c 整理知识 本文在debian6 0上运行通过 xff0c 内核版本为2 6 32 学习LDD中网络驱动
  • 认识(大端--小端)端模式

    span style color 000000 端模式 xff08 Endian xff09 的这个词出自Jonathan Swift书写的 格列佛游记 这本书根据将鸡蛋敲开的方法不同将所有的人分为两类 xff0c 从圆头开始将鸡蛋敲开的人
  • HOW TO install nam for ns2 on debian

    Debian is convinent to install software packages for the tool aptl Like many other packages we can use apt get install n
  • c++ #pragma once和 #ifndef 优缺点对比分析

    pragma once ifndef方式为了避免同一个头文件被包含 xff08 include xff09 多次 pragma once 声明 定义语句 ifndef SOMEFILE H define SOMEFILE H 声明 定义语句
  • roslaunch找不到packge

    roslaunch找不到packge 尝试下面几种做法 1 source bashrc 2 source catkin ws devel setup bash 3 rospack profile 为确保ROS能找到新包 xff0c 常常在发
  • DSP:TMS320C6657 之 UART波特率问题

    6657 设置串口波特率 以614400为例 xff08 1 xff09 根据公式计算分频系数 xff08 2 xff09 1GHz 主频下 UART 输入频率 166666666Hz xff08 1 6 xff09 xff08 3 xff
  • 手写httpServer Demo案例

    相信每一个java程序猿在学习javaWeb的时候 xff0c 或多或少接触了Servlet 或者说通过Servlet来完成页面发送的请求 今天 xff0c 模仿Servlet接受和处理请求实现一个简单的httpServer 该Server
  • ubuntu18.04 查看在用串口

    1 终端输入cutecom 打开串口助手 xff0c 可能没有下载 xff0c 可根据提示下载安装 sudo cutecom 2 点击device旁边的下拉按钮即可查询当前在用的串口
  • Linux解决未定义的引用过程记录

    Linux解决未定义的引用过程记录 在摸索vscode使用的过程中 xff0c 编写的代码出现了为定义的引用错误 csdn上搜索了很多 xff0c 代码小白看完觉得写的非常的简略 xff0c 完全无从下手 xff08 应该是我太菜了 xff
  • 十一种室内定位传感器方案汇总介绍与对比(机器人、物联网领域)

    室内定位传感器方案汇总 目录 室内定位传感器方案汇总 1 定位方案概述 1 1 内定位系统有最基本的5种算法 xff1a 1 2 常用的室内定位技术主要包括以下几种 xff1a 1 3 定位理论 1 4 不同的定位方案对比 2 各种定位方案
  • C++中的unique函数

    STL中的unique函数的头文件 xff1a span class hljs preprocessor include lt iostream gt span unique 的作用是 去掉 容器中相邻元素的重复元素 xff0c 这里所说的
  • 单片机开发入门---从零开始玩转FRDM-KL25Z

    一 背景介绍 最近需要开发一个程序 xff0c 使用飞思卡尔的开发板FRDM KL25Z xff0c 来设计一款 西蒙游戏 的改进版 xff0c 下面我们先来了解一下西蒙游戏 西蒙游戏 是一款益智休闲类小游戏 xff0c 它的游戏规则是 x
  • SSD---系统架构

    SSD主要由两大模块构成 主控和闪存介质 另外可选的还有Cache缓存单元 主控是SSD的大脑 xff0c 承担着指挥 运算和协调的作用 xff0c 具体表现在 xff1a 前端实现标准主机接口与主机通信 xff0c 接口包括SATA SA
  • SSD核心技术---FTL

    FTL算法的优劣与否 xff0c 直接决定了SSD在性能 xff08 Performance xff09 可靠性 xff08 Reliability xff09 耐用性 xff08 Endurance xff09 等方面的好坏 xff0c

随机推荐

  • SSD---PCIe介绍

    SSD已经大跨步迈入PCIe时代 作为SSD的一项重要技术 xff0c 我们有必要对PCIe有个基本的了解
  • SSD---NVMe介绍

    何为NVMe xff1f NVMe即Non Volatile Memory Express xff0c 是非易失性存储器标准 xff0c 是跑在PCIe接口上的协议标准 NVMe的设计之初就有充分利用了PCIe SSD的低延时以及并行性 x
  • SSD---ECC原理

    我们知道 xff0c 所有型号的闪存都无法保证存储的数据会永久稳定 xff0c 这时候就需要ECC xff08 纠错码 xff09 去给闪存纠错 ECC能力的强弱直接影响到SSD的使用寿命和可靠性 本章将简单介绍ECC的基本原理和目前最主流
  • 音响发烧友---HiFi音频功放

    最近一直想做个开源的电子项目 xff0c 思考许久还是选择做个HiFi音频功放 作为一个音响发烧友 xff0c 带大家DIY一台属于自己的功放 聆听一下 xff0c 纯正的音乐之美 首选需要了解一下功放的类型 xff1a 纯甲类功率放大器乙
  • Altium Designer20常用使用快捷键

    一 AD20常用快捷键 PCB布线常使用 xff1a ctrl 43 m 测量长度 Q 单位切换 shift 43 ctrl 43 r 取消显示标注 shift 43 S 显示层切换 ctrl 43 右击 高亮显示一条线 ctrl 43 D
  • Altium Designer20 交叉选择模式

    在使用Altium Designer进行PCB布局时 xff0c 首先我们需要将原理图元器件更新到PCB中 xff0c 所有的元件封装都会汇集到PCB中 xff0c 但并没有根据电路模块进行分类聚集 xff1b 我们可以使用AD的交叉选择模
  • Altium Designer20 批量修改元件丝印大小和位置

    在进行PCB布线时 xff0c 我们经常需要调整元件丝印的大小和位置 有了丝印才能在PCB焊接和调试板子的时候得心应手 xff0c 下面介绍一种便捷的方法 xff0c 来实现批量修改元件丝印和位置 1 修改元件丝印大小 xff08 1 xf
  • 图像重叠区域

    http www cnblogs com dwdxdy archive 2013 08 02 3232331 html
  • bat批处理---实现输入指定拷贝文件

    在windows平台下 xff0c 平常的给芯片下载程序过程中 xff0c 经常遇到需要在多个文件夹下面拷贝bin文件的情况 xff0c 为了实现能够通过输入参数 xff0c 来选择需要拷贝的问下 xff0c 写了一个 bat批处理文件 只
  • Altium Designer20 PCB规则设置

    我们在进行PCB布线之前 xff0c 需要对PCB布线进行规则设置 xff0c 如果大家只是DIY爱好者 xff0c 那我们将设置价格最经济的PCB规则 xff0c 我们可以以捷配官网的PCB工艺信息作为参考 xff1b 下面我将介绍常用的
  • 入门到放弃之 NVMe-MI --- 协议简介

    在学习NVMe MI协议之前 xff0c 感觉协议是如此的枯燥 xff0c 通过短时间的阅读Spec发现协议规范定义的精妙绝伦 xff1b 协议中各种细节处理的相当到位 xff0c 最有趣的是消息服务模型的状态机设计 xff0c 希望大家一
  • NVMe-MI --- Message Transport(消息传输)

    3 消息传输 该规范定义了一个支持多种消息传输的接口 消息格式与带外机制和带内隧道机制相同 3 1 NVMe MI消息 NVMe MI消息在带外机制和带内隧道机制中都有使用 NVMe MI消息的格式如图17和图18所示 在带外机制中 xff
  • NVMe-MI --- Message Servicing Model(消息服务模型)

    4 消息服务模型 4 1 NVMe MI 消息 图23展示了NVMe MI消息的分类 NVMe MI消息的两个主要类别是请求消息和响应消息 当使用带外机制时 xff0c 请求消息由管理控制器发送到管理端点 在使用带内隧道机制时 xff0c
  • NVMe-MI --- Management Interface Command Set

    Management Interface Command Set 命令集定义了当NMIMT值被设置为NVMe MI命令时 xff0c 请求者可以提交的命令信息 管理接口命令集同时适用于带外机制和带内隧道机制 NVMe MI消息结构以及所有N
  • PCIe总线引脚定义

    然后看一下PCI E的接口定义 这就是显卡插口前面的那段短的金手指 xff0c 就是这段 xff1a 这一段负责供电 SMBus和感知设备是否插上 xff0c 对于数据的传输作用不大 xff0c 所以不用深究 用浅绿色标出来的是检测插槽上设
  • Mbus新增主动报警功能,简单问题的波折路程。

    由于用到了主动报警上传功能 一个简单的if判断 xff0c 便实现了判断与上传功能 脱机测试 xff0c 上流量台测试 xff0c 都正常 以为这件事便了了 结果到了现场却给暴出了问题 xff0c 没法收到报警 于是 xff0c 一对一的现
  • 相机IMU联合标定

    单目相机内参标定 xff1a xff08 焦距 xff0c 光心 https blog csdn net qq 42399848 article details 89298212 ops request misc 61 257B 2522r
  • Hough变换理解

    reference http blog csdn net app 12062011 article details 11307053 一 简单介绍 Hough变换是图像处理中从图像识别几何形状的基本方法之一 xff0c 霍夫变换寻找直线和圆
  • 1.常见8种排序算法分析笔记之-空间O(1)三种(冒泡、选择、插入)

    文章目录 空间复杂度为O 1 的三种排序法一 冒泡排序法1 代码实现思想2 复杂度分析3 代码实现4 代码测试 二 选择排序法1 代码实现思想2 复杂度分析3 代码实现4 代码测试 三 插入排序法1 代码实现思想2 复杂度分析3 代码实现4
  • 2.常见8种排序算法分析笔记之-时间O(NlogN)三种(归并、快排、堆排序)

    文章目录 时间复杂度为 O N l o g