【数据结构与算法】排序

最近结合学习了排序算法,进行一下总结以供后续复习。(每个算法基于数组实现,升序排序。)

  • 所有排序算法的总结比较:
排序算法平均时间复杂度最坏情况时间复杂度额外空间复杂度稳定性
冒泡排序 O ( n 2 ) {O(n^2)} O(n2) O ( n 2 ) {O(n^2)} O(n2) O ( 1 ) {O(1)} O(1)稳定
选择排序 O ( n 2 ) {O(n^2)} O(n2) O ( n 2 ) {O(n^2)} O(n2) O ( 1 ) {O(1)} O(1)不稳定
插入排序 O ( n 2 ) {O(n^2)} O(n2) O ( n 2 ) {O(n^2)} O(n2) O ( 1 ) {O(1)} O(1)稳定
希尔排序 O ( n d ) {O(n^d)} O(nd) O ( n 2 ) {O(n^2)} O(n2) O ( 1 ) {O(1)} O(1)不稳定
堆排序 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)不稳定
归并排序 O ( n l o g n ) {O(nlogn)} O(nlogn) O ( n l o g n ) {O(nlogn)} O(nlogn) O ( n ) {O(n)} O(n)稳定
快速排序 O ( n l o g n ) {O(nlogn)} O(nlogn) O ( n 2 ) {O(n^2)} O(n2) O ( l o g n ) {O(logn)} O(logn)不稳定
计数排序 O ( n + k ) {O(n+k)} O(n+k) O ( n 2 ) {O(n^2)} O(n2) O ( n ) {O(n)} O(n)稳定
桶排序 O ( n + k ) {O(n+k)} O(n+k) O ( n + k ) {O(n+k)} O(n+k) O ( n + k ) {O(n+k)} O(n+k)稳定
基数排序 O ( n ∗ k ) {O(n*k)} O(nk) O ( n ∗ k ) {O(n*k)} O(nk) O ( n ∗ k ) {O(n*k)} O(nk)稳定
  • 插入排序、冒泡排序、希尔排序、快速排序的效率与待排数据的原始状态有关;
  • 当待排记录序列按关键字顺序有序时,插入排序、冒泡排序能达到0 ( n ) 的时间复杂度;快速排序为 О ( n ^2 );
  • 采用希尔方法排序时,若关键字的排列杂乱无序,则效率最高;
  • 选择排序、堆排序、归并排序的效率与待排数据的原始状态无关;

 

T = O ( n 2 ) {T=O(n^2)} T=O(n2)

 

冒泡排序(Bubble Sort)

  • 一个最简单的冒泡排序:每次找到待排序列中的最小元素,放到前面。(但这个算法相比于真正的冒泡排序来说效率还是非常低的)
for( i = 0; i < length - 1; i++) // 循环length次,每次找到对应待排序列中最小的元素放到a[i]
	for( j = i + 1; j < length; j++) //这个循环目的是找到 i+1 至 length-1 间最小元素放到a[i]
		if(a[j] < a[i] ) 交换; 
  • 下面是正宗的冒泡排序:
status flag = true;
for( i = 0; i < length - 1 && flag; i++) { // i 从 0 到倒数第 2
	flag=false;	
	for( j = length - 2; j >= i; j--) { // j 从倒数第 2 到 i 往前循环
		if( a[j+1] < a[j] ) { 
			交换;
			flag = true; 
		} 	
	}	
}
  1. 举例
    有个数组 a[] = {9,1,5,8,3,7,4,6,2},length = 9。注:a的下标是从 0 到 length-1 的
    比如说下面的排序: (标亮的是待排序列,没标亮的是排好的序列)
    9,1,5,8,3,7,4,6,2
    1,9,2,5,8,3,7,4,6
    1,2,9,3,5,8,4,7,6
    1,2,3,9,4,5,8,6,7
    1,2,3,4,9,5,6,8,7
    1,2,3,4,5,9,6,7,8
    1,2,3,4,5,6,9,7,8
    1,2,3,4,5,6,7,9,8
    1,2,3,4,5,6,7,8,9(排序完成)
    每次找待排序列中最小的元素提到前面来,就像每次剩下的气泡中,最小的气泡慢慢浮到上面来一样。
  2. 分析一下这个算法比上面的改进:上面的算法只会每次找到最小的元素,也可能在每次循环时不知不觉增加了逆序对的个数。而这个算法只会把不断地把较小的元素交换到前面,肯定会减少逆序对的个数,总的交换次数肯定比前者少,因此效率较高!
    (注:对于下标 i < j , 如果 A [ i ] > A [ j ] , 则称 ( i , j ) 是一对逆序对 (inversion)。要提高算法的效率,就得在每次交换元素时尽可能多的消去逆序对!)
  3. 核心思想:两两比较相邻元素,如果反序则交换,直到没有反序的记录为止
  4. 冒泡排序最好情况下,比较次数为 n-1,交换次数为 0,时间复杂度为 O ( N ) {O(N)} O(N);最坏情况下,比较次数为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1),交换次数为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1)时间复杂度为 O ( N 2 ) {O(N^2)} O(N2)
  5. 算法稳定
     

选择排序(Selection Sort)

for( i = 0; i < length - 1; i++) {  // i 从 0 到倒数第 2
	for( j = i + 1; j < length; j++) { // j 从 i+1 到最后一个元素
		if(a[j] < a[min] ) { 
			min = j; 
		}
	}
	if(min != i)  交换a[i]与a[min]}
  1. 核心思想:每次找出待排序列中最小元素的下标,再和第 i 个元素交换
  2. 选择排序无论最好还是最坏情况都比较次数为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1),最好的时候交换次数为 0,最坏的时候交换次数为n-1。时间复杂度为 O ( N 2 ) {O(N^2)} O(N2)
  3. 算法不稳定
  4. 选择排序相对于冒泡排序,交换数据次数相当少,因此选择排序性能要优于冒泡排序
     

插入排序(Insertion Sort)

void InsertSort(int a[], int length) {
	int i, j, tmp;
	for (i = 1; i < length; i++) {  // i 从第 2 个元素到最后一个元素
		tmp = a[i];
		for (j = i; j > 0 && tmp < a[j - 1]; j--) { // j从i 到tmp不再小于a[j-1]之时
			a[j] = a[j - 1]; // a[j-1]向后移
		}
		a[j] = tmp; // 最后 tmp 插入到正确位置
	}
}
  1. 核心思想:将一个元素插入到有序表中
  2. 插入排序在最好的情况下,比较次数为 n-1,移动次数为 0,时间复杂度 O(N);最坏的情况下,比较次数为 2 + 3 + 4 + … … + n = ( n + 2 ) ( n − 1 ) 2 2+3+4+……+n=\frac{(n+2)(n-1)}{2} 2+3+4++n=2(n+2)(n1),移动次数为 ∑ i = 1 n ( i + 1 ) = ( n + 4 ) ( n − 1 ) 2 \sum_{i=1}^{n}{(i+1)} = \frac{(n+4)(n-1)}{2} i=1n(i+1)=2(n+4)(n1),平均比较和移动次数为 n 2 4 \frac{n^2}{4} 4n2时间复杂度为 O ( N 2 ) {O(N^2)} O(N2)
  3. 算法稳定
  4. 插入排序 比 冒泡排序 和 选择排序 的比较和移动次数都有降低,性能相对好一些
     

T = O ( n d ) {T = O(n^d)} T=O(nd)

 

希尔排序(Shell Sort)

for(k = 0, d = increment[k]; k < increment的长度; d = increment[++k]) { // 希尔增量序列

	// 插入排序
	for(i = d; i < length; i++) { // i 从 d 到 length-1
		tmp = a[i];
		for(j = i; j >= d && tmp < a[j-d]; j-=d) { // j从i 到tmp不再小于a[j-d]之时
			a[j] = a[j-d]; // a[j-d]向后移
		}
		a[j] = tmp; // 最后 tmp 插入到正确位置
	}
}
  1. 举例
    对序列:{ 9 1 5 8 3 7 4 6 2 } 进行排序:
    希尔排序举例
  2. 核心思想:将相距某个增量的序列组成一个子序列,分别在每个子序列中进行插入排序,这样得到的总序列基本有序(所谓基本有序,就是小的关键字基本在前面,大的关键字基本在后面,不大不小的基本在中间,这样就可以有效减少逆序对的个数。)最后再对全体进行一次插入排序。
  3. 如果使用 Hibbard 增量序列 D k = 2 k − 1 {D_k=2^k-1} Dk=2k1(也就是相邻元素互质)的时候,可以取得不错的效率,这时最坏时间复杂度为 Θ ( N 3 2 ) {Θ(N^\frac{3}{2})} Θ(N23)平均时间复杂度约为 O ( N 5 4 ) {O(N^\frac{5}{4})} O(N45)。除此之外还有其他增量序列,不再一一列举。
  4. 由于元素的比较与交换是跳跃式进行,因此算法不稳定
     

T = O ( n l o g n ) {T=O(nlogn)} T=O(nlogn)

 

堆排序(Heap Sort)

void swap(int& a, int& b) {
    int tmp = a; a = b; b = tmp;
}

// 调节堆:从 s根节点开始沿较大的孩子结点向下调整堆(建立在大顶堆已构建的基础上)
void HeapAdjust(int a[], int s, int m) {
    int temp, j;
    temp = a[s];  // 取出根节点 s 存放的值

    // 主要思想:沿较大的孩子结点向下筛选
    for (j = 2 * s + 1; j <= m; j = 2 * j + 1) {

        // 把j调整成较大的孩子的下标
        if (j < m && a[j] < a[j + 1]) ++j; 

        /* 将较大孩子与父结点比较,大的话就把值赋给父结点,
         然后下滤 temp 直到给 temp 找到了合适的位置————
         也就是 temp 大于它的两个孩子的时候 break 退出循环,
         或者等到循环结束的时候让 temp 变为叶节点。 */
        if (a[j] >= temp) { 
            a[s] = a[j]; 
            s = j;
        }
        else break;
    }

    // 把 temp 放到最终的位置
    a[s] = temp; 
}

// 堆排序(适用于元素从0开始的情况)
void HeapSort(int a[], int length) {
    int i;

    // 把 a 调成一个大根堆(从最后一个根节点开始自底向上,对每个结点 HeapAdjust)
    for (i = (length - 2) / 2; i >= 0; --i) 
        HeapAdjust(a, i, length - 1);

    // 循环执行下面两步:
    for (i = length - 1; i > 0; i--) {
        swap(a[0], a[i]);       // 将根节点(最大的元素)与最后一个元素交换
        HeapAdjust(a, 0, i - 1);   // 将除去最大元素之后的 a[0..i-1] 重新调整为大根堆
    }
}
  • 相关概念
    • 大顶堆:每个结点的值都大于等于其左右孩子结点的值;
    • 小顶堆:每个结点的值都小于等于其左右孩子结点的值
    • 在堆排序中,元素下标从1开始,则对于下标为 i 的元素,其左、右孩子的下标分别为2i, 2i+1,如果 i>1,则其双亲是结点 i/2
    • 在堆排序中,元素下标从0开始,则对于下标为 i 的元素,其左、右孩子的下标分别为2i+1, 2i+2,如果 i>1,则其双亲是结点 (i-1)/2
    • 如果按层序遍历的方式给结点从1开始编号,那么节点之间满足如下关系:
      k i ≥ k 2 i , k i ≥ k 2 i + 1 ( 或 者 是 ≤ ) {k_i \ge k_{2i},k_i \ge k_{2i+1} (或者是\leq)} kik2ikik2i+1 ,并且 1 ≤ i ≤ n / 2 {1 \leq i \leq n/2} 1in/2
  • 算法流程:将序列建成最大堆(从最后一个根节点开始自底向上,对每个结点 HeapAdjust),然后循环进行以下步骤:【将根节点(也就是最大元素)与最后一个元素交换,换到最后排除出去,再把剩下的元素继续 HeapAdjust 调整成最大堆】。这样最后得到的序列就是升序序列。
  • 堆排序最好、最坏情况时间复杂度都是 O ( n l o g n ) {O(nlogn)} O(nlogn)
  • 由于元素的比较与交换是跳跃式进行,因此算法不稳定
  • 堆排序在性能上要远远好过 O ( n 2 ) {O(n^2)} O(n2)的算法,不过虽然堆排序平均时间复杂度较小,但其实实际效果不如用增量序列的希尔排序。另外,由于初始堆的构建比较次数较多,堆排序并不适合序列个数较少的情况。
     

归并排序(Merging Sort)

  • 归并排序(递归)
// 将有序的 a[L..M] 和 a[M+1..L] 归并为有序的 tmp[L..R]
void Merge(int a[], int tmp[], int L, int M, int R) {
	int LBegin = L, RBegin = M + 1;	
	int k = L;
	while (LBegin <= M && RBegin <= R) { // 将 a 中记录由小到大地并入 tmp
		if (a[LBegin] < a[RBegin]) {
			tmp[k++] = a[LBegin++];
		}
		else {
			tmp[k++] = a[RBegin++];
		}
	}
	while (LBegin <= M) { // 直接复制左边剩下的
		tmp[k++] = a[LBegin++];
	}
	while (RBegin <= R) { // 或者直接复制右边剩下的
		tmp[k++] = a[RBegin++];
	}
}

// 将SR[s..t]归并排序为TR1[s..t]
void MSort(int SR[], int TR1[], int s, int t) {
	int m;
	int TR2[MAX + 1];
	if (s == t) // 递归出口:L == R(只剩一个元素时)
		TR1[s] = SR[s];
	else {
		m = (s + t) / 2;
		MSort(SR, TR2, s, m);		// 递归解决左边
		MSort(SR, TR2, m + 1, t);	// 递归解决右边
		Merge(TR2, TR1, s, m, t);	// 将归并两段有序序列
	}
}

// 归并排序(递归)对外接口
void MergeSort(int a[],int n) {
	int* tmp = (int*)malloc((n - 1) * sizeof(int));
	MSort(a, a, 0, n - 1); //将a[]归并排序为tmp[]
}
  • 归并排序(非递归)
// 将有序的 a[L..M] 和 a[M+1..L] 归并为有序的 tmp[L..R]
void Merge(int a[], int tmp[], int L, int M, int R) {
	int LBegin = L, RBegin = M + 1;	
	int k = L;
	while (LBegin <= M && RBegin <= R) { // 将 a 中记录由小到大地并入 tmp
		if (a[LBegin] < a[RBegin]) {
			tmp[k++] = a[LBegin++];
		}
		else {
			tmp[k++] = a[RBegin++];
		}
	}
	while (LBegin <= M) { // 直接复制左边剩下的
		tmp[k++] = a[LBegin++];
	}
	while (RBegin <= R) { // 或者直接复制右边剩下的
		tmp[k++] = a[RBegin++];
	}
}

// 将SR[]中相邻长度为s的子序列两两归并到TR[]
void MergePass(int SR[], int TR[], int k, int n) {
	int i = 0, j;
	while (i + 2 * k < n) { // 两两归并
		Merge(SR, TR, i, i + k - 1, i + 2 * k - 1);	
		i += 2 * k;
	}
	if (i + k - 1 < n - 1) { // 归并最后两个序列	
		Merge(SR, TR, i, i + k - 1, n - 1); 
	}
	else { // 若最后只剩下单个子序列
		for (j = i; j < n; j++) {
			TR[j] = SR[j];
		}
	}	
}

// 归并排序(非递归)
void MergeSort2(int a[], int n) {
	int* tmp = (int*)malloc((n - 1) * sizeof(int));
	int k = 1; // 子序列长度
	while (k <= n) {
		MergePass(a, tmp, k, n); k *= 2; // 子序列长度加倍
		MergePass(tmp, a, k, n); k *= 2; // 子序列长度加倍
	}
}
  • 主函数测试
#include<iostream>
using namespace std;
#define MAX 100
int main() {
	int a1[] = { 50,10,90,30,70,40,80,60,20 };
	int a2[] = { 50,10,90,30,70,40,80,60,20 };
	int length = 9;
	int i;
	MergeSort(a1, length);
	MergeSort2(a2, length);
	for (i = 0; i < length; i++) cout << a1[i] << " ";
	cout << endl;
	for (i = 0; i < length; i++) cout << a2[i] << " ";
	return 0;
}
  1. 归并排序图示
    非递归-归并排序
  2. 核心思想:有序子列的归并。每次比较A指针和B指针指向位置的哪个元素更小,放到C指针位置
  3. 归并排序需要额外空间,适用于外排序。
  4. 不管什么时候,时间复杂度都是 O ( n l o g n ) {O(nlogn)} O(nlogn)。使用递归的空间复杂度是 O ( n + l o g n ) {O(n+logn)} O(n+logn),非递归的的空间复杂度是 O ( n ) {O(n}) O(n)。因归并排序的非递归算法性能较好。
  5. 算法稳定
     

快速排序(Quick Sort)

void swap(int& a, int& b) {
    int tmp = a; a = b; b = tmp;
}

int Partition(int a[], int low, int high) {

	// 三数取中
	int mid = low + (high - low) / 2;
	if (a[low] > a[high]) swap(a[low], a[high]);
	if (a[mid] > a[high]) swap(a[high], a[mid]);
	if (a[mid] > a[low]) swap(a[mid], a[low]);

	int pivotkey = a[low];
	
	// 从表的两端交替地向中间扫描
	while (low < high) { 
		while (low < high && a[high] >= pivotkey) high--;
		a[low] = a[high];
		while (low < high && a[low] <= pivotkey) low++;
		a[high] = a[low];
	}

	a[low] = pivotkey;
	return low; // 返回基准所在位置
}

void QuickSort(int a[], int low, int high) {
	int pivot;
	if (low < high) {
		pivot = Partition(a, low, high); // 分区
		QuickSort(a, low, pivot - 1); // 左边递归排序
		QuickSort(a, pivot + 1, high); // 右边递归排序
	}
}
  • 基本思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
  • 最好情况下,Partition每次都划分的很均匀,时间复杂度为 O ( n l o g n ) {O(nlogn)} O(nlogn);最坏情况下,序列是正序或逆序的,时间复杂度为 O ( n 2 ) {O(n^2)} O(n2)平均情况 , 时间复杂度为 O ( n l o g n ) {O(nlogn)} O(nlogn)
  • 就空间复杂度来说 , 主要是递归造成的栈空间的使用 , 最好情况 , 递归树的深度为 l o g 2 n {log_2n} log2n , 其空间复杂度也就为 O ( l o g n ) {O(logn)} O(logn) , 最坏情况 , 需要进行 n -1 次递归调用 , 其空间复杂度为 O ( n ) {O(n)} O(n) , 平均情况 , 空间复杂度也为 O ( n l o g n ) {O(nlogn)} O(nlogn)
  • 算法不稳定
  • 快速排序优化:
    • 由于快速排序用到了递归操作,如果数组非常小,快速排序反而不如直接插入排序来得更好(直接插入是简单排序中性能最好的 ) 。因此我们增加了一个判断 , 当 high - low 不大于某个常数时 ( 有资料认为 7 比较合适 ,也有认为 50 更合理 , 实际应用可适当调整 ) , 就用直接插入排序 , 这样就能保证最大化地利用两种排序的优势来完成排序工作。
    • QuickSort 函数在其尾部有两次递归操作,如果待排序的序列划分极端不平衡 , 递归深度将趋近于 n , 而不是平衡时的 log2n , 这就不仅仅是速度快慢的问题了。栈的大小是很有限的 , 每次递归调用都会耗费一定的栈空间 , 函数的参数越多 , 每次递归耗费的空间也越多。因此如果能减少递归 , 将会大大提高性能。
// 快速排序优化算法
void QuickSort1(int a[], int low, int high, int n) {
	int pivot;
	if ((high - low) > MAX_LENGTH_INSERT_SORT) {
		while (low < high) {
			pivot = Partition(a, low, high); // 分区
			QuickSort1(a, low, pivot - 1,n); // 左边递归排序
			low = pivot + 1; // 尾递归
		}
	}
	else InsertSort(a, n); 
}

 

T = O ( n + k ) {T=O(n+k)} T=O(n+k)

 

计数排序(Counting Sort)

  • 计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数
  • 算法流程:
    • 找出待排序的数组中最大和最小的元素;
    • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
    • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
    • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
  • 计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。
     

桶排序(Bucket Sort)

  • 桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
  • 算法流程:
    • 设置一个定量的数组当作空桶;
    • 遍历输入数据,并且把数据一个一个放到对应的桶里去;
    • 对每个不是空的桶进行排序;
    • 从不是空的桶里把排好序的数据拼接起来。
  • 桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少,但相应的空间消耗就会增大。
     

T = O ( n ∗ k ) {T=O(n*k)} T=O(nk)

 

基数排序(Radix Sort)

  • 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
  • 算法流程:
    • 取得数组中的最大数,并取得位数;
    • arr为原始数组,从最低位开始取每个位组成radix数组;
    • 对radix进行计数排序(利用计数排序适用于小范围数的特点);
  • 基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。
  • 基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java提供了多种数据结构算法排序的实现。以下是一些常见的排序算法: 1. 冒泡排序(Bubble Sort):通过不断比较相邻元素并交换,将最大(或最小)的元素逐渐“冒泡”到最后(或最前)位置。时间复杂度为O(n^2)。 2. 选择排序(Selection Sort):每次选择未排序部分中最小(或最大)的元素,放到已排序部分的末尾(或开头)。时间复杂度为O(n^2)。 3. 插入排序(Insertion Sort):将未排序部分的元素逐个插入已排序部分的正确位置。时间复杂度为O(n^2)。 4. 快速排序(Quick Sort):通过选择一个基准元素,将列表分为两个子列表,其中一个子列表的所有元素都小于(或大于)基准元素,然后递归地排序两个子列表。时间复杂度平均情况为O(n log n)。 5. 归并排序(Merge Sort):将列表拆分为多个子列表,递归地对每个子列表进行排序,然后合并这些有序子列表以获得最终有序列表。时间复杂度为O(n log n)。 6. 堆排序(Heap Sort):将待排序元素构建成一个最大(或最小)堆,然后逐个移除堆顶元素并将其放入已排序部分。时间复杂度为O(n log n)。 7. 希尔排序(Shell Sort):将列表按照一定的间隔分组,并对每个分组进行插入排序,然后逐渐缩小间隔,直到间隔为1,最后再进行一次插入排序。时间复杂度取决于间隔序列的选择。 Java中提供了Arrays类和Collections类来对数组和集合进行排序,可以使用它们的sort方法。另外,你也可以自己实现这些排序算法

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值