排序算法之交换排序( 冒泡排序 & 快速排序(三种实现方法及其优化,非递归) ) ( C语言版 )

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。

这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

冒泡排序算法的原理如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

  • 冒泡排序最好情况时间复杂度O(n),冒泡排序最坏情况下时间复杂度O(n^2)  冒泡排序空间复杂度O(1)  冒泡排序是一种稳定的排序算法

代码如下 :

//冒泡排序
//使用flag作为标记,当flag为1时,证明数组已将为有序数组,直接可以跳出循环
void BubbleSort(int *a, int len)
{
	assert(a);
	int flag = 0;
	for (int i = 0; i < len - 1; i++){
		flag = 1;
		for (int j = 0; j < len - i - 1; j++){
                        //比较是否需要交换
			if (a[j] > a[j+1]){
				Swap(&a[j], &a[j + 1]);
				flag = 0;
			}
		}
		//如果数组已经有序则直接跳出循环
		if (flag)
			break;
	}
}

 

快速排序(Quicksort)是对冒泡排序的一种改进。

快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据以一个枢纽div 分割成独立的两部分,其中左半部分的所有数据都比div要小,右半部分的所有数据都比div要大,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

递归实现 : 

//快速排序
void QuickSort(int *a, int left, int right){
	if (left >= right)
		return;
	assert(a);
	//按照基准经left和right标记区间划分成两个部分
	int div = PartionOne(a, left, right);
	//排列左半部分
	QuickSort(a, left, div - 1);
	//排列右半部分
	QuickSort(a, div + 1, right);
}

Partion()为进行一次库快速排序的算法, 常见的方法有 左右指针法 , 挖坑法 , 前后指针法

左右指针法

  1. 这里选取数组中最后一个数( 第一个数 )作为关键字key
  2. 设置两个变量begin和end分别数组的起始和结束下标
  3. begin从前往后, 当找到一个比key大的值时, right从后往前走, 当找到一个比key小的值时, 将其交换
  4. 继续重复上述步骤, 直到begin和end相遇, 再将key记录的数放到begin的位置, 并且返回begin

    

如上图所示 , 当begin>=end时, 一趟快速排序就完成了, 并在最后将key和begin的值进行交换,结 果就为 16  25  25  34  58  49

代码实现如下 : 

//(左右指针法)寻找一个枢轴作为记录
int PartionOne(int *a, int begin, int end)
{
	//选取序列的末尾作为关键字
	int key = a[end];
        //index用来记录关键字所在的下标
	int index = end;
	//保证key的左边都小于key,右边都大于key
	while (begin < end){
		//从左向右寻找比key大的数
		while (begin < end && a[begin] <= key)
			begin++;
		//从右向左寻找比key小的数
		while (begin < end && a[end] >= key)
			end--;
		//交换
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[index]);
	return begin;
}

挖坑法

  1. 这里选取数组中最后一个数( 第一个数 )作为关键字key, 也为初始坑位
  2. 设置两个变量begin和end分别数组的起始和结束下标
  3. begin从前往后, 当找到一个比key大的值时, 将该数放到坑中, 这时的坑位就变作begin
  4. right从后往前走, 当找到一个比key小的值时, 将该数放到坑中, 这时的坑位就变作end
  5. 重复上述步骤直到begin和end相遇, 在将key放入到最后一个坑位

  

当begin>=end时, 将key放到最后一个坑中, 就完成了一次排序

代码如下 : 

//挖坑法
int PartionTwo(int *a, int begin, int end)
{
	//key为关键字,记录第一个坑里边的数据
	int key = a[end];
	while (begin < end){
		while (begin < end && a[begin] <= key)
			begin++;
		a[end] = a[begin];
		while (begin < end && a[end] >= key)
			end--;
		a[begin] = a[end];
	}
	a[begin] = key;
	return begin;
}

前后指针法

  1. 定义一个变量next指向序列的起始位置, 定义一个变量prev指向next的前一个位置
  2. 当a[next]>key时, next不断向前走, 当a[next]<key时且prev和next相邻时, next和prev同时向前走
  3. 当a[next]<key且prev和next不相邻时, 将交换a[next]和a[prev]进行交换

     

代码如下 : 

//前后指针法
int PrationThree(int *a, int begin, int end)
{
	assert(a);
	if (begin >= end)
		return -1;
	int key = a[end];
	int prev = begin - 1;
	int next = begin;
	while (next < end){
		//如果找到小于key的值,并且nxet和prev不相邻时进行交换。注意两个条件的先后位置不能更换
		if (a[next] < key && ++prev != next)
			Swap(&a[prev], &a[next]);
		next++;
	}
	//注意:prev记得+1
	Swap(&a[end], &a[++prev]);
	return prev;
}

快速排序的优化

  1. 三数取中法 : 当所要排序的数组本身就是正序或者逆序时, 这样我们每次挑选的枢纽并没有起到划分的作用, 这是快速排序的效率会减低很多, 所以这里可以可以每次从起始位置, 中间位置和结束位置当中挑选出中间值, 将这个中间值和key交换, 这样就会使得每次划分接近于均等, 效率会提高不少
  2. 小区间法优化 (直接插入) :  当要排序的序列比较少时,为了避免开辟过多栈帧,使用插入排序来提高快速排序的效率

插入排序传送门 --- https://blog.csdn.net/ds19980228/article/details/82589860

代码如下 : 

//三数取中法
int GetMid(int *a, int left, int right)
{
	assert(a);
	int mid = left + ((right - left) >> 2);
	if (a[left] <= a[right]){
		if (a[mid] < a[left])
			return left;
		else if (a[mid]>a[right])
			return right;
		else
			return mid;
	}
	else{
		if (a[mid] > a[left])
			return left;
		else if (a[mid] < a[right])
			return right;
		else
			return mid;
	}
}
//(左右指针法)寻找一个枢轴作为记录
int PartionOne(int *a, int begin, int end)
{
        //mid用获取起始,中间和结束位置中的中间值
	//选取序列的末尾作为关键字
	int mid = GetMid(a, begin, end);
	Swap(&a[mid], &a[end]);
	int key = a[end];
	int index = end;
	//保证key的左边都小于key,右边都大于key
	while (begin < end){
		//从左向右寻找比key大的数
		while (begin < end && a[begin] <= key)
			begin++;
		//从右向左寻找比key小的数
		while (begin < end && a[end] >= key)
			end--;
		//交换
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[index]);
	return begin;
}
//快速排序(小区间优化)
void QuickSortTwo(int *a, int left, int right){
	if (left >= right)
		return;
	assert(a);
	//当要排序的序列比较少时,为了避免开辟过多栈帧,使用插入排序
	if (right - left <= 3){
		for (int i = 0; i < right-1; i++){
			int index = i + 1;
			int cur = a[index];
			if (a[i]>a[index]){
				while (index > 0 && cur > a[index + 1]){
					a[index + 1] = a[index];
					++index;
				}
			}
			a[index] = cur;
		}
	}
	//按照基准经left和right标记区间划分成两个部分
	int div = PrationOne(a, left, right);
	//排列左半部分
	QuickSort(a, left, div - 1);
	//排列右半部分
	QuickSort(a, div + 1, right);
}

快速排序 ( 非递归 )

在使用递归时, 主要就是为了划分子区间, 其本身也就相当于压栈的过程, 当我们要实现非递归时, 只需要使用一个栈将下标区间保存起来即可    栈传送门 --- https://blog.csdn.net/ds19980228/article/details/81741617

代码如下 : 

//快速排序(非递归)使用栈来存放下标区间
void QuickSortRecNone(int *a, int left, int right){
	if (left >= right)
		return;
	assert(a);
	Stack s;
	StackInit(&s);
	StackPush(&s, left);
	StackPush(&s, right);
	while (StackEmpty(&s) != 0){
		int end = StackTop(&s);
		StackPop(&s);
		int begin = StackTop(&s);
		StackPop(&s);
		int div = PartionOne(a, begin, end);
		//将左右区间分别压栈
		if (begin < div - 1){
			StackPush(&s, begin);
			StackPush(&s, div - 1);
		}
		if (end > div + 1){
			StackPush(&s, div + 1);
			StackPush(&s, end);
		}
	}
}

完整代码如下 : 

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <string.h>
//栈的头文件
#include "Stack.h"

//交换数据
void Swap(int *m, int *n)
{
	int tmp = *m;
	*m = *n;
	*n = tmp;
}
//三数取中法
int GetMid(int *a, int left, int right)
{
	assert(a);
	int mid = left + ((right - left) >> 2);
	if (a[left] <= a[right]){
		if (a[mid] < a[left])
			return left;
		else if (a[mid]>a[right])
			return right;
		else
			return mid;
	}
	else{
		if (a[mid] > a[left])
			return left;
		else if (a[mid] < a[right])
			return right;
		else
			return mid;
	}
}
//(左右指针法)寻找一个枢轴作为记录
int PartionOne(int *a, int begin, int end)
{
	//选取序列的末尾作为关键字
	int mid = GetMid(a, begin, end);
	Swap(&a[mid], &a[end]);
	int key = a[end];
	int index = end;
	//保证key的左边都小于key,右边都大于key
	while (begin < end){
		//从左向右寻找比key大的数
		while (begin < end && a[begin] <= key)
			begin++;
		//从右向左寻找比key小的数
		while (begin < end && a[end] >= key)
			end--;
		//交换
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[index]);
	return begin;
}
//挖坑法
int PartionTwo(int *a, int begin, int end)
{
	//key为关键字,记录第一个坑里边的数据
	int key = a[end];
	while (begin < end){
		while (begin < end && a[begin] <= key)
			begin++;
		a[end] = a[begin];
		while (begin < end && a[end] >= key)
			end--;
		a[begin] = a[end];
	}
	a[begin] = key;
	return begin;
}
//前后指针法
int PrationThree(int *a, int begin, int end)
{
	assert(a);
	if (begin >= end)
		return -1;
	int key = a[end];
	int prev = begin - 1;
	int next = begin;
	while (next < end){
		//如果找到小于key的值,并且nxet和prev不相邻时进行交换。注意两个条件的先后位置不能更换
		if (a[next] < key && ++prev != next)
			Swap(&a[prev], &a[next]);
		next++;
	}
	//注意:prev记得+1
	Swap(&a[end], &a[++prev]);
	return prev;
}
//快速排序
void QuickSort(int *a, int left, int right){
	if (left >= right)
		return;
	assert(a);
	//按照基准经left和right标记区间划分成两个部分
	int div = PartionOne(a, left, right);
	//int div = PartionTwo(a, left, right);
	//int div = PrationThree(a, left, right);
	//排列左半部分
	QuickSort(a, left, div - 1);
	//排列右半部分
	QuickSort(a, div + 1, right);
}
//快速排序(小区间优化)
void QuickSortTwo(int *a, int left, int right){
	if (left >= right)
		return;
	assert(a);
	//当要排序的序列比较少时,为了避免开辟过多栈帧,使用插入排序
	if (right - left <= 3){
		for (int i = 0; i < right-1; i++){
			int index = i + 1;
			int cur = a[index];
			if (a[i]>a[index]){
				while (index > 0 && cur > a[index + 1]){
					a[index + 1] = a[index];
					++index;
				}
			}
			a[index] = cur;
		}
	}
	//按照基准经left和right标记区间划分成两个部分
	//int div = PartionOne(a, left, right);
	//int div = PartionTwo(a, left, right);
	int div = PrationThree(a, left, right);
	//排列左半部分
	QuickSort(a, left, div - 1);
	//排列右半部分
	QuickSort(a, div + 1, right);
}
//快速排序(非递归)使用栈来存放下标区间
void QuickSortRecNone(int *a, int left, int right){
	if (left >= right)
		return;
	assert(a);
	Stack s;
	StackInit(&s);
	StackPush(&s, left);
	StackPush(&s, right);
	while (StackEmpty(&s) != 0){
		int end = StackTop(&s);
		StackPop(&s);
		int begin = StackTop(&s);
		StackPop(&s);
		int div = PartionOne(a, begin, end);
		//将左右区间分别压栈
		if (begin < div - 1){
			StackPush(&s, begin);
			StackPush(&s, div - 1);
		}
		if (end > div + 1){
			StackPush(&s, div + 1);
			StackPush(&s, end);
		}
	}
}
//打印数组
void PrintArray(int *a, int len)
{
	assert(a);
	for (int i = 0; i < len; i++)
		printf("%d ", a[i]);
	printf("\n");
}
int main()
{
	int a[] = { 58, 25, 49, 25, 16, 34 };
	QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
	//QuickSortTwo(a, 0, sizeof(a) / sizeof(int)-1);
	//QuickSortRecNone(a, 0, sizeof(a) / sizeof(int)-1)0;
	PrintArray(a, sizeof(a) / sizeof(int));
	system("pause");
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值