常见排序算法

本文介绍了7种常见的比较类排序算法,包括插入排序、希尔排序、选择排序、堆排序,以及交换排序中的冒泡排序和快速排序,和归并排序。这些算法的时间复杂度和空间复杂度各有不同,适用于不同的场景。其中,插入排序和冒泡排序在元素接近有序时效率较高,而快速排序和归并排序具有较好的平均性能,归并排序在处理大量数据时特别有用,但需要额外空间。
摘要由CSDN通过智能技术生成

常见的排序算法有7种,如下图:

这7种算一个大类,叫做比较类排序,通过比较的方法来进行排序。

而非比较类有计数排序、桶排序、基数排序。

注意:本文使用C语言编写程序,且全部默认升序排序。

一、插入排序

1、插入排序

插入排序程序:

void InsertSort(int* a, int n) {
	for (int i = 0; i < n - 1; ++i) {
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0 && tmp < a[end]) {
			a[end + 1] = a[end];
			end--;
		}
		a[end + 1] = tmp;
	}
}

排序arr[0, n-1],元素共有n个。

插入排序是把排序分成arr[0,end](有序)和arr[end+1,n-1](无序),然后在把这个arr[end+1]插入到前面arr[0,end](有序)中,使arr[0,end+1]变得有序。

原理动态图:(自己做)

特性:

时间复杂度:平均O(N^2),最好时O(N),最坏时O(N^2)

空间复杂度:O(1)

稳定

元素集合越接近有序,直接插入排序的时间效率就越高

2、希尔排序

希尔排序程序:

void ShellSort(int* a, int n) {
	int gap = n;
	while (gap > 1) {
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i += gap) {
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0 && tmp < a[end]) {
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = tmp;
		}
	}
}

希尔排序是直接插入排序的优化,它是把每个间隔为gap的元素分成一组,然后先对这组进行直接插入排序,这样让整体接近有序,经过多组排序后,最后gap为1时,整体几乎为有序,此时时间效率几乎接近O(N)。

原理动态图:(自己做)

 特性:

时间复杂度:平均O(N^1.3) ,最好时O(N),最坏时O(N^2)

空间复杂度:O(1)

不稳定

gap>1时都是预排序,让整体变得接近有序,gap == 1时,整体已经接近有序,很快排完序

希尔排序是直接插入排序的优化

二、选择排序

1、选择排序

选择排序程序:

void SelectSort(int* a, int n) {
	for (int i = 0; i < n-1; ++i) {
		int num = i;
		for (int j = i+1; j < n; ++j) {
			if (a[j] < a[num]) {
				num = j;
			}
		}
		int tmp = a[i];
		a[i] = a[num];
		a[num] = tmp;
	}
}

选择排序也叫简单选择排序,或者直接选择排序

原理图:(自己做)

特性:

时间复杂度:O(N^2)

空间复杂度:O(1)

不稳定

选择排序思路很好理解,但效率不好,实际中还少用

优化后的选择排序:

void SelectSort2(int* a, int n) {
	int begin = 0, end = n - 1;
	while (begin < end) {
		int mini = begin, maxi = end;
		for (int i = begin; i <= end; ++i) {
			if (a[i] > a[maxi]) maxi = i;
			if (a[i] < a[mini]) mini = i;
		}

		int tmp = a[begin];
		a[begin] = a[mini];
		a[mini] = tmp;
		//如果beign原位置为maxi,就把交换后的位置给maxi
		if (maxi == begin) maxi = mini; 

		tmp = a[end];
		a[end] = a[maxi];
		a[maxi] = tmp;
		++begin;
		--end;
	}
}

每次循环都能找到最小值和最大值,一次循环找两个极值,减少一半的时间复杂度。但是平均时间复杂度还是O(N^2),

2、堆排序

堆排序程序:

void Swap(int* q, int* p) {
	int tmp = *q;
	*q = *p;
	*p = tmp;
}

void AdjustDown(int* a, int n, int parent) {
	int LargerChild = parent * 2 + 1;
	
	while (LargerChild < n) {
		//找到最大的孩子:如果右孩子存在且大于左孩子,则选右孩子
		if (LargerChild + 1 < n && a[LargerChild + 1] > a[LargerChild]) {
			++LargerChild;
		}
		if (a[LargerChild] > a[parent]) {
			Swap(&a[LargerChild], &a[parent]);
			parent = LargerChild;
			LargerChild = parent * 2 + 1;
		}else {
			break;
		}
	}
}

void HeapSort(int* a, int n) {
	//建大堆 (整体大的在高层)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
		AdjustDown(a, n, i);
	}
	//把最大的数放在最后,且最后这位不在参与前面的排序
	for (int i = n - 1; i > 0; --i) {
		Swap(&a[0], &a[i]);
		AdjustDown(a, i, 0);
	}
}

原理图:(出于https://www.cnblogs.com/jx-dx/p/3756248.html

特性:

时间复杂度:O(N*logN)

空间复杂度:O(1)

不稳定

堆排序使用堆来选数,效率高了许多。

三、交换排序

1、冒泡排序

冒泡排序程序:

void BubbleSort(int* a, int n) {
	for (int i = 0; i < n - 1; ++i) {
		for (int j = 0; j < n - 1 - i; ++j) {
			if (a[j + 1] < a[j]) {
				int tmp = a[j + 1];
				a[j + 1] = a[j];
				a[j] = tmp;
			}
		}
	}
}

冒泡排序是一种交换排序,通过不断的交换,把最大值放在最后,或最小值放在最前。

原理动态图: (出于知乎十大经典排序算法及动图演示 - 知乎)

 特性:

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定

冒泡排序是很容易理解的排序

2、快速排序

快速排序程序:

void Swap(int* q, int* p) {
	int tmp = *q;
	*q = *p;
	*p = tmp;
}

int PartitionSort(int* a, int left, int right) {
	int keyi = left;
	while (left < right) {
		while(left < right && a[right] >= a[keyi]) {
			--right;//一直找到小于a[keyi]的位置
		}
		while (left < right && a[left] <= a[keyi]) {
			++left;//一直找到大于a[keyi]的位置
		}//把小于a[keyi]放在a[keyi]左边,大于a[keyi]放在a[keyi]左边
		if(left < right) Swap(&a[left], &a[right]);
	}//把a[keyi]的值放在小于a[keyi]和大于a[keyi]的中间,之后a[left]表示这个中间值
	Swap(&a[left], &a[keyi]);
	//传回中间值的位置,即left(left位置和keyi位置的值换了,但是指向中间值的是left)
	return left;
}

void QuickSort(int* a, int left, int right) {
	if (left < right) {
		int keyi = PartitionSort(a, left, right);
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	return;
}

快速排序是采用分治的思想:(hoare版)

选取一个关键值,把小于关键值的值放在关键值的左边,大于关键值的值放在关键值的右边

然后让小于关键值的这部分快速排序,让大于关键值的这部分快速排序

最后小于关键值的部分是有序的,大于关键值的部分也是有序,即整体变得有序。

原理动态图: (出于知乎十大经典排序算法及动图演示 - 知乎)

 特性:

时间复杂度:O(N*logN)

空间复杂度:O(logN)

不稳定

快速排序的整体综合性能和使用场景都是比较好。

(C++中algorthm库中sort使用的就是快速排序)

四、归并排序

1、归并排序

归并排序程序:(二路归并)

void _MergeSort(int* a, int begin, int end, int* tmp) {
	if (begin >= end) return;
	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2]) tmp[i++] = a[begin1++];
		else tmp[i++] = a[begin2++];
	}
	while (begin1 <= end1) tmp[i++] = a[begin1++];
	while (begin2 <= end2) tmp[i++] = a[begin2++];
	//归并的那部分,就拷贝回那部分
	memcpy(a+begin, tmp+begin, (end - begin + 1)*sizeof(int));
	//用循环拷贝
	//for (int i = begin; i <= end; ++i) a[i] = tmp[i];
}

void MergeSort(int* a, int n) {
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL) {
		perror("malloc fail!");
		return;
	}
	_MergeSort(a, 0, n-1, tmp);
	free(tmp);
	tmp = NULL;
}

原理图:(出于https://www.cnblogs.com/pierceming/p/12775351.html)

原理动态图:(出于知乎十大经典排序算法及动图演示 - 知乎

特性:

时间复杂度:O(N*logN)

空间复杂度:O(N)

稳定

归并排序的缺点是需要O(N) 的空间复杂度,归并排序更多是解决磁盘中的外排序问题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值