【学习点滴】c++手写常用排序算法:堆排序、快排、归并

目录

堆排序:

归并排序:

快排:

插入排序:

冒泡排序:

选择排序:


 

堆排序:

void heap_adjust(vector &a, int i,int len) {
	int lc = 2 * i + 1;
	int rc = 2 * i + 2;

	int maxindex = i, tmp;
	if (lc < len && a[lc] > a[maxindex]) maxindex = lc;
	if (rc < len && a[rc] > a[maxindex]) maxindex = rc;

	if (maxindex != i) {
		tmp = a[i];
		a[i] = a[maxindex];
		a[maxindex] = tmp;
		heap_adjust(a, maxindex, len);	//递归调整被换节点的子树
	}
}

void heap_sort(vector & a) {
	int tmp = 0;
	int len = a.size();
	//make_heap
	for (int i = len / 2 - 1; i >= 0; i--) {	//len / 2 - 1是从下到上从右到左的第一个非叶子节点
		heap_adjust(a,i,len);
	}

	//sort
	for (int i = len - 1; i >= 1; i--) {
		tmp = a[i];
		a[i] = a[0];
		a[0] = tmp;
		heap_adjust(a,0,i);		//将前面的i-1个元素再调整为大顶堆
	}
}

实现了一个利用最大堆实现的数组从小到大排序方法。调用时传入一个vector数组即可。

主要步骤:

1,对传入的数组a进行建堆操作,即将a调整为一个最大堆。(从len / 2 - 1处的元素到0处元素,每个都与其带的子树进行调整)

2,将a[0]即堆顶元素与a的最后一个元素交换,实现了将最大元素放在正确位置上的操作,然后对前面n-1个元素进行堆调整,再次成为最大堆

3,循环进行n-1次即可。(因为在最后一次i=1时,会将上一次调好的堆中仅剩的两元素交换位置,即变成从小到大的顺序。)

图解:https://www.cnblogs.com/wanglei5205/p/8733524.html

 

 

归并排序:

分而治之的思想

void merge(vector &a, int l, int mid ,int r, vector& tmp) {
	int i = l, j = mid + 1;
	int k = 0;
	while (i <= mid && j <= r) {
		if (a[i] <= a[j]) {
			tmp[k++] = a[i++];
		}
		else
			tmp[k++] = a[j++];
	}
	while (i <= mid) tmp[k++] = a[i++];
	while (j <= r) tmp[k++] = a[j++];

	for (int q = 0; q < k; q++)
		a[l + q] = tmp[q];	//l+q表示第几个小组
}

void merge_sort(vector &a, int l, int r, vector& tmp) {    //递归
	if (l < r) {
		int mid = (l + r) >> 1;
		merge_sort(a, l, mid, tmp);
		merge_sort(a, mid + 1, r, tmp);
		merge(a, l, mid, r, tmp);
	}
}

使用:

    vectortmp(a.size(), 0);    //先建好一个数组,免得在递归里面反复创建
    merge_sort(a,0,a.size()-1,tmp);

 

快排:

思路:找一个pivot(此处选择第一个元素),先从右向左搜索,一旦发现比pivot小的元素就与pivot交换位置,然后从左往右搜索,直到发现比pivot大的元素就交换位置,重复此过程直到搜索位置标识 l>=r ,此时pivot就放在了正确的位置上,他左边的元素都比他小,并且右边的元素都比他大。然后递归对左右两边重复进行上述步骤即可,时间复杂度O(nlogn),最坏是O(n^2)快排的交换使得排序成为不稳定的。

void quick_sort(vector& a, int l,int r){
	if(l& a,int l,int r){
	int pivot=a[l];
	while(lpivot) r--;        //找到右侧比pivot小的第一位
		if(l

上面的partition是通过“挖坑”来交换的,也可以先将l和r走到要交换的位置直接swap,其实本质是一样的:

int partition_swap(vector& a, int l, int r) {
	int pivot = a[l];
	int i = l;
	while (l < r) {
		while ( l pivot) r--;
		while ( l

 

从空间性能上看,尽管快速排序只需要一个元素的辅助空间,但快速排序需要一个栈空间来实现递归。最好的情况下,即快速排序的每一趟排序都将元素序列均匀地分割成长度相近的两个子表,所需栈的最大深度为log2(n+1);但最坏的情况下,栈的最大深度为n。这样,快速排序的空间复杂度为O(log n))。

 

优化:

三路划分的快速排序(荷兰国旗问题)

我们上面的样本没有出现有大量重复关键字的情况,但是这种情况在实际中很常见,那么我们如何改进呢?

一种直接的方法是将文件划分成三个部分:一部分比划分元素小,一部分比划分元素大,还有一部分等于划分元素,如图:

完成这样的过程比两路划分更为复杂,Bentley和Mcllory于1993年发明了一种三路划分的方法:它把标准的划分过程做了如下改进:扫描时把遇到的左子文件中与基准值key相等的元素放到文件的最左边,然后把右子文件中与划分元素相等的元素放到文件的最右边,如图;

【学习点滴】c++手写常用排序算法:堆排序、快排、归并_第1张图片

即使在没有重复关键字的时候,该方法也会运行的很好

代码如下:

struct TwoInt {
	int lr;
	int rl;
};


TwoInt partition_3way(vector& a, int l, int r) {
	int lr = l - 1, rl = r + 1;
	int pivot= a[l];
	int i = l + 1;
	while (i <= r && i < rl) {
		if (a[i] < pivot) {
			swap(a[++lr], a[i++]);
		}
		else if (a[i] > pivot) {
			swap(a[i], a[--rl]);
			if (i >= rl) {
				break;
			}
			continue;
		}
		else {
			i++;	//相等元素不动
		}
	}
	TwoInt ti;
	ti.lr = lr;
	ti.rl = rl;
	return ti;
}

void quick_sort_3way(vector& a, int l, int r) {
	if (l >= r) {
		return;
	}
	TwoInt ti = partition_3way(a, l, r);	//获得左边界和右边界
	quick_sort_3way(a, l, ti.lr);
	quick_sort_3way(a, ti.rl, r);
}

【学习点滴】c++手写常用排序算法:堆排序、快排、归并_第2张图片

 

 

插入排序:

适用于元素基本有序的情况,仅需很少的数据移动。 令i从0~n,当for到第i个元素时,前面a0~ai-1 个元素已经是排好序的了,此时只需将第ai个元素与前面的进行比较,插到合适的位置即可,并将此位置的元素都向后移动(通过两两比较交换实现)。时间复杂度O(n^2), 稳定。

void insert_sort(int a[], int n){
	if(a==nullptr || n<2)
		return ;
	for(int i=1;i=0 && a[j]>a[j+1] ; j--)
			swap(a,j,j+1);
	}
}


//其中
void swap(int a[],int l,int r){
	int tmp=a[l];
	a[l]=a[r];
	a[r]=tmp;
}

 

冒泡排序:

一个大框框,每次缩一格,在框框内逐次沉底,实现小元素冒泡的效果。O(n^2) ,稳定

void bubble_sort(int a[], int n){
	for(int i=n-1;i>0;--i)
	  for(int j=0;ja[j+1])
		  swap(a,j,j+1);
}

改进:

  • 第一步优化

如果上面代码中,里面一层循环在某次扫描中没有执行交换,则说明此时数组已经全部有序列,无需再扫描了。因此,增加一个标记,每次发生交换,就标记,如果某次循环完没有标记,则说明已经完成排序。

void bubble_sort(int a[], int n){
    bool flag = true;
	for(int i=n-1;i>0;--i){
            flag = false;
	    for(int j=0;ja[j+1]){
		      swap(a,j,j+1);
                      flag = true;
                   }
            }
            if(flag == false)
                break;        //内层没有交换,表明已经有序了,直接退出
        }
}

 

 

选择排序:

初始时在序列中找到最小元素,将其换到起始位置作为已排序列,再从剩余元素中继续寻找最小,换到后一位,以此类推。O(n^2),不稳定。

void select_sort(int a[],int n){
	int pos,i,j;
	for(i=0;ia[j])
				pos=j;
			if(pos!=i)
				swap(a,pos,i);
	}
}

 

总结:

排序法 平均时间 最差情形 稳定度 额外空间 备注
冒泡 O(n2) O(n2) 稳定 O(1) n小时较好
选择 O(n2) O(n2) 不稳定 O(1) n小时较好
插入 O(n2) O(n2) 稳定 O(1) 大部分已排序时较好
基数 O(logRB) O(logRB) 稳定 O(n)

B是真数(0-9),

R是基数(个十百)

Shell O(nlogn) O(ns) 1 不稳定 O(1) s是所选分组
快速 O(nlogn) O(n2) 不稳定 O(logn) n大时较好
归并 O(nlogn) O(nlogn) 稳定 O(n) n大时较好
O(nlogn) O(nlogn) 不稳定 O(1) n大时较好

 

你可能感兴趣的:(笔记)