八大排序算法总结

最近复习了排序算法,所以在这篇博客里总结一下。

这里的八大排序有:冒泡排序、快速排序、直接插入排序、shell排序、选择排序、堆排、归并排序和基数排序。

因为基数排序没看,所以下面只会出现前面7种排序,之后可能会加上把。。。

首先简单分个类

  1. 交换:冒泡排序、快速排序
  2. 插入:直接插入排序、shell排序
  3. 选择:选择排序、堆排
  4. 分配:归并排序、基数排序

下面就一个一个来看把。

一、冒泡排序

算法:从数组末尾开始,两个两个比较,小的交换到前面,大的交换到后面,就像泡泡一样,小的数慢慢交换到最前面

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

空间复杂度:O(1)

稳定性:稳定

代码:

void bubble_sort(int arr[],int low,int high)
{
	bool flg = true;//记录一趟中是否进行了交换

	for (int i = low; i < high&&flg; ++i)//趟数
	{
		flg = false;//默认没有交换

		for (int j = high; j > i; --j)//一趟遍历
		{
			if (arr[j - 1] > arr[j])//前面的数大,则交换
			{
				swap(arr[j], arr[j - 1]);
				flg = true;//交换了,标志位置为true
			}
		}
	}
}

两层循环,外循环为一共的趟数,因为一趟遍历下来会有一个最小的数排序完成,一共个n个数,只剩下最后一个数的时候不用再走一趟了,所以是n-1趟。内循环为一趟遍历,从最后两个开始遍历,两个数中小的放前面,大的放后面,及前面的数大则进行交换。代码中的flg目的是记录一趟遍历中有没有交换,如果没有交换,那么整个排序肯定也完成了,此时不用继续循环了。

二、快速排序

算法:让要排序数的左边的数都小于它,右边的数都大于它,递归这个过程,使得整个数组有序。

时间复杂度:平均:O(N*log2N)       最好:O(N*log2N)        最差:O(N^2)

空间复杂度:O(log2n)~O(n) 

稳定性:不稳定

代码:

void insert_sort(int arr[], int low, int high)//插入排序
{
	for (int i = low + 1; i <= high; ++i)	//将arr[i]插入有序[low..i-1]部分
	{
		int sentry = arr[i];
		int j;
		for (j = i - 1; j >= low && sentry < arr[j]; --j)
			arr[j + 1] = arr[j];
		arr[j + 1] = sentry;
	}
}

int partition(int arr[], int low, int high)
{
	int pivotKey = arr[low]; //用子序列的首个元素作为枢轴元素。
	while (low < high)	//从数组的两端交替向间扫描,只有1个元素需要调整吗?不需要。
	{
		while (low < high && arr[high] >= pivotKey) --high;	//从高端向低端扫描。
		arr[low] = arr[high];	//较小值交换到低端。
		while (low < high && arr[low] <= pivotKey) ++low;	//从低端向高端扫描。
		arr[high] = arr[low];	//较大值交换到高端。
	}
	arr[low] = pivotKey;	//枢轴元素归位。
	return low;
}

void quick_sort(int arr[], int low, int high)
{
	//只有序列中元素个数超过10,才需要进行递归调用。
	if (high - low > 10)
	{
		while (low < high)
		{
			int pivotLoc = partition(arr, low, high);	//找到划分位置。
			quick_sort(arr, low, pivotLoc - 1);
			low = pivotLoc + 1; //消除尾递归。
		}
	}
	else
		insert_sort(arr, low, high);
}

在元素较少的时候用递归不适合,所以用了插入排序,插入排序下面还会提到。快排有两部分组成,一个主体函数和一个分割函数,分割函数的任务:使待排序数左边数都小于它,右边数都大于它(实际上就是确定了这个数的位置),主体函数在对这个数的左右两边再次分割排序(也就是调用递归),最后整个数组便都有序了。

三、直接插入排序

算法:将待排序数插入到已经有序的数组中。

例如:刚开始时,第一个元素可视为有序的,那么将第二个数插入它,就能得到一个有两个有序数的数组,再把第三个数插入这个有两个有序数的数组……以此类推完成排序。

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

空间复杂度:O(1)

稳定性:稳定

代码:

void insert_sort(int arr[],int low,int high)
{
	for (int i = low + 1; i < high + 1; ++i)
	{
		int temp = arr[i];//待排序数
		int j = i - 1;
		for (; j >= low && temp < arr[j]; --j)//再有序数组中找插入位置
		{
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = temp;//找到位置后插入
	}
}

总的过程就是从前往后排序,先把前面的排成有序数组,再把无序的数和有序数组比较,找到一个位置插入。

四、shell排序

算法:插入排序的优化,再插入排序的基础上加入了分组的概念,减少了待排序元素的个数

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

空间复杂度:O(1)

稳定性:不稳定

代码:

void shell_sort(int arr[], int low, int high)
{
	int increase = high - low + 1; //增量初始值为序列[low..high]元素个数。
	do
	{
		increase = increase / 3 + 1;
		for (int i = increase; i <= high; ++i)将arr[i]插入有序子序列中。
		{
			int temp = arr[i];
			int j = i - increase;
			for (; j >= low && temp < arr[j]; j -= increase)//在有序子序列范围内,元素后移寻找插入位置。
			{
				arr[j + increase] = arr[j];
			}
			arr[j + increase] = temp;
		}

	} while (increase > 1);
}

五、选择排序

算法:把数组中最小的选出来,放到最前面,循环这个过程

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

空间复杂度:O(1)

稳定性:不稳定

代码:

void select_sort(int arr[], int low, int high)
{
	for (int i = low; i < high; ++i)
	{
		int min = i;//记录最小值下标
		for (int j = i + 1; j <= high; ++j)
		{
			if (arr[j] < arr[min])//找到最小值
			{
				min = j;//更新当前最小值
			}
		}
		if (min != i)//把最小值往前提
		{
			swap(arr[i], arr[min]);
		}
	}
}

过程比较简单,用下标记录最小值的位置,遍历数组,找到最小的值,交换到前面,排完就是从小到大的一个数组。

六、堆排

算法:堆排序是排序算法中比较难的一个,需要进行模块化降低难度。抽象出3个动作:1)堆调整;2)构建最大堆;
3)堆排序。初始构建堆,在重建堆的反复筛选。

时间复杂度:平均:O(N*log2N)       最好:O(N*log2N)       最坏:O(N*log2N)

空间复杂度:O(1)

稳定性:不稳定

//[low+1..high]符合最大堆,需要调整使得[low..high]符合最大堆。
void heap_adjust(int arr[], int low, int high)
{
	int sentry = arr[low];
	int parent = low;	//parent指针始终指向当前双亲。
	int child = parent * 2 + 1;	//child指针始终指向当前孩子。
	while(child <= high)
	{
		if(child < high && arr[child] < arr[child+1]) ++child;	//找到较大的孩子。
		if(sentry > arr[child]) break;	//当前parent指针指向的位置就是sentry值应该放置的位置。
		arr[parent] = arr[child];	//孩子值上移。
		parent = child;	//更新parent使之指向孩子。
		child = 2 * parent +1;	//更新child使之指向新的parent的孩子。
	}
	arr[parent] = sentry;
}
//将[low..high]构建成最大堆
void make_heap(int arr[], int low, int high)
{
	//找到最后一个非叶子(终端)结点。
	int pos = (high-1) / 2;
	//由最后一个非叶子结点开始,从下往上从右往左进行调整。
	while(pos >= low)
	{
		heap_adjust(arr, pos, high);
		--pos;
	}
}
//结合构建最大堆函数和堆调整函数进行堆排序
void heap_sort(int arr[], int low, int high)
{
	//构建成最大堆。
	make_heap(arr, low, high);
	
	//每趟将堆顶元素交换到尾部并分离,对剩下的元素周而复始进行相同的操作。遍量i的含义是大根堆的结束位置。
	for(int i = high; i > low; --i)
	{
		swap(arr[low], arr[i]);
		heap_adjust(arr, low, i-1);	//[low+1,i-1]是符合大根堆的,通过调整使[low..i-1]符合大根堆。
	}
}

主要通过heap_adjust()函数来构建大根堆,在这个函数中,会把数组调整成每个父节点都大于它的孩子节点,这样调整完成后,就会有根节点是数组的最大值,通过这个来进行排序。

七、归并排序

算法:也是使用了分割的思想,以中间点为分割,前后两部分比较,按大小放入另一个辅助数组,递归排序

时间复杂度:平均:O(N*log2N)       最好:O(N*log2N)       最坏:O(N*log2N)

空间复杂度:O(n)

稳定性:稳定

void two_way_merge(int sr[], int tr[], int low, int mid, int high)
{
	//下标i,j,k分别标记第一二归并段和结果集合。
	int i = low, j = mid+1, k = low;
	while(i <= mid && j <= high)	//循环继续的条件是i和j都未达到相应归并段的末尾。
	{
		if(sr[i] < sr[j])
		{
			tr[k++] = sr[i++];
		}
		else
		{
			tr[k++] = sr[j++];
		}
	}
	//如果sr[low..mid]有剩余元素,进行处理。
	while(i <= mid)
	{
		tr[k++] = sr[i++];
	}
	//如果sr[mid+1..high]有剩余元素,进行处理。
	while(j <= high)
	{
		tr[k++] = sr[j++];
	}
}
//将第一个参数所代表的元素就地排序,tmp[low..high]是临时空间。
//要求sr[low..high]与tmp[low..high]元素完全一样。
void rmerge(int sr[], int tmp[], int low, int high)
{
	if(low == high)	//只有1个元素默认已经有序不做任何处理。
	{
		return ;
	}
	else
	{
		int mid = low + ((high-low)>>1);	//将[low..high]剖成两半
		//先使tmp[low..mid]有序。
		rmerge(tmp, sr, low, mid);
		//再使tmp[mid+1..high]有序。
		rmerge(tmp, sr, mid+1, high);
		//最后将有序tmp[low..mid]和tmp[mid+1..high]归并到sr[low..high]。
		two_way_merge(tmp, sr, low, mid, high);
	}
}
//归并排序
void merge_sort(int arr[], int n)
{
	if(NULL == arr || n <= 1)
		return ;
	int *tmp = new int[n];
	assert(NULL != tmp);
	for(int i = 0; i < n; ++i)
	{
		tmp[i] = arr[i];
	}
	
	rmerge(arr, tmp, 0, n-1);
	
	delete[] tmp;	//释放堆区内存。
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值