十种排序算法

在文章的开始呢,这里先准备一些数据结构以及算法的可视化网站,可以让大家更直观的看到算法的运行过程:

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

首先需要申明的是,大部分的排序算法是通过交换来调整数字之间的相对顺序的,因此交换的部分是一个需要经常用到的函数,所以在介绍以下十种排序算法之间呢,我们先写一个检测交换函数:

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

在下面的介绍中,我们将从各种排序算法的时间复杂度,空间复杂度,稳定性以及算法适用于什么样的场景来进行介绍,前面那个指标的含义我就不介绍了,稳定性是指:值相等的两个元素的相对顺序在排序前后不改变

1最基础的冒泡排序

回到梦开始的地方,大二的那个夏天,被计算机基础的老师叫上黑板首先冒泡排序,冒泡排序采用沉底的方法,从第一个数开始,与右侧相邻的数字比较,当左右相对顺序逆序就交换,否则的话就不交换。
每一套排序之后,将会将最大的数字沉底,后面每一套比较需要对比的数字就减1.

void Bubble(int arr[] ,int L)
{
	for (int i = 0; i < L; i++)
	{
		for (int loca = 0; loca < L - i-1; loca++)
		{
			if (arr[loca] > arr[loca + 1])
			{
				swap(arr[loca], arr[loca + 1]);
			}
		}
	}
}

2插入排序

从字面上理解的意思,插入排序就是将没有排序的数字插入到已经排序的队列之中。
思想: 一共有i套(0<=i<n);第i套将第i个数字与前面的分别比较,遇到大的数就交换,否则就不交换,其中,我们最常见的版本就是这个:

void InsetSort2(int arr[], int L)
{
	for (int i = 1; i < L; i++)
	{
		for (int j =i ; j >0; j--)
		{
			if (arr[j] < arr[j - 1])
			{
				swap(arr[j], arr[j - 1]);
			}
		}
	}
}

在这种排序方式中,带排序的数字会与以排序中的每一个数字都进行比较,如果逆序就交换;我们没有办法改变交换的次数,但是却可以改变判断的次数,改变插入排序的一个好的方法的原则是:
***待排序的数字往前开始比较,当遇到下一个数字比起小,那前面的所有数字都会比其小,就没有必要再次判断。***这个概念很重要,因为插入排序的特点是对于部分有序的数组排序效率会很高,后面的希尔排序我会讲到这一点。
改进的插入排序代码如下:

void InsetSort(int arr[],int L)
{
	for (int i = 0; i < L; i++)
	{
		int j = i;
		while (j != 0 && arr[j] < arr[j - 1])
		{
			swap(arr[j], arr[j - 1]);
			j--;
		}
	}
}

选择排序

规则:第一轮,将第一个数字与后面的所有数字依次比较,如果逆序就交换,如此之后,第一次数字就是最大或者最小的值。
第二轮,将第二个数字与后面的所有数字依次比较,如果逆序就交换,如果之后,第二个数字就是第二大或者第二小的值。
。。。。

void choiceSort(int arr[],int L)
{
	for (int i = 0; i < L; i++)
	{
		for (int j = i + 1; j < L; j++)
		{
			if (arr[i] > arr[j])
			{
				swap(arr[i], arr[j]);
			}
		}
	}
}

选择排序算法是不稳定的,为什么?这里举一个简单的列子:
对 5 8 5* 2 9 进行排序,只要一轮我们会发现结果为: 2 8 5* 5 9
5* 和 5的相对顺序就变了,这是因为比5小的第一数字2在5*的右边,发生交换之后,顺序就会改变。所以牢记这一点:选择排序是不稳定的排序!

4快速排序

思想,采用分而治之的思想,通过不断定位基准数的位置,从而再对基准数两边的位置跑排序。
首先使用分治算法的方法,一下是基本的原则:
1,左右指针分别指向数组的开始和末尾
2、选定一个基准数,这里我们设置最左边的数字为基准书
3、从右指针开始,找到小于等于基准数的数字,从左指针开始,找到大于基准数的数字(注意,如果你想要第一套排序之后等于基准数的数字都在左边,采用此方法),找到之后,交换左右指针数值,继续次过程
(PS:第三点有两点注意事项:1,等于号只能有一边,因为一套排序过程的目标是是的左边的数字都小于基准数,右边大于等于基准数,或者左边的数字小于等于基准数,右边大于基准数字,这个需要在等于号后面控制。2、一定要特别注意,远离基准数的指针先移动!!!!!
后面循环往复。。。
代码的时候,其实快排算法的分治算法写法都是一样的讨论,一定要记清楚上面的要点。
代码:

void quickSort(int arr[],int left,int right)
{
	if (left >= right)
	{
		return;
	}
	int base = left;
	int end = right;
	while (left<right)
	{
		while (arr[right] > arr[base] && left<right)
		{
			right--;
		}
		while (arr[left] <= arr[base] && left<right)
		{
			left++;
		}
		swap(arr[left], arr[right]);
		cout << "交换"<< endl;
	}
	swap(arr[base], arr[left]);
	cout << "交换" << endl;
	quickSort(arr, base, left - 1);
	quickSort(arr, left + 1, end);
}

这一部分留给填坑法。

希尔排序

希尔排序来源于插入排序。将大规模无序数组拆分成小规模有序数组,然后使用插入排序。希尔排序需要掌握三个要点:
1、缩小增量公式,希尔排序的时间复杂度根据不同的增量递减公式会有不同的表现,我一般常用的减小公式为 incre = incre/3 +1;(从数组长度开始),缩小增量公式一定要牢记啊!!!
2、分组
3、选择排序

void ShellDivide(int arr[], int L)
{

	int incre = L;
	while (incre > 1)
	{
		incre = incre/3 + 1;
		//增量确定了
		for (int i = 0; i < incre; i++)
		{
			//需要排序的起始位置知道了,相当于序列也就知道了
			for (int base = 0; base < L; base+=incre)
			{
				for (int j = base; j < L; j+=incre)
				{
					if (arr[base] > arr[j])
					{
						swap(arr[base], arr[j]);
					}
				}
			}
		}
	}
}

归并排序

下午花了两个小时才把归并排序从理解到代码实现的过程完成了,现在开始叙述一篇。
在了解归并排序之前,我不得不提一下在力扣官网刷到的第二个简单题,给两个有序数组合并,因为在归并排序中很好地利用了这中题目的解法。
1、归并排序采用了先分后合的思想,采用二分法将arr分割成不同层次的单元素数组为止。
2、在合并的过程中,采用了有序列表合并的思想。
3、使用递归的方法是,要注意分割和合并的顺序,采用左右分割的思想,递归的顺序采用后跟遍历的思想。先分左边,再分右边,先分的后合,这其中的机理我到现在还没有弄明白,不过可以先这样写着。
一下是代码:

void mergeSort(int arr[];int left,int right)
{
if(left<right) //只有当left <right 的时候才有必要分割
{
	int mid = (left+right)/2;
	mergeSort(arr,left,middle);
	mergeSort(arr,middle+1,right);
	merge(arr,left,middle,right);
}

}

其中 merg函数实际上就是排列有序链表的过程,也就是说arr[left,middle],
arr[middle+1,right];实际上是两个有序链表,现在将他们合并就行。
以上的核心是,在实现递归的时候要递归基是哪一个,也就是在什么情况下跳出递归。

void merge(int arr[].left ,mid,right)
{
		int *p  =new int[right-left+1];
		int k=0;
		int st =left;
		int end =right;
		right = mid;
		while(left<=mid && right<=end)
		{
		if(arr[left] <=arr[right])
		{
			p[k++] = arr[left++];
		}
		else
		{
			p[k++] = arr[right++];
		}
}
		while(left<=middle)
		{
		p[k++] =arr[left++];
}
		while(right<=end)
		{
		p[k++] =arr[right++];
		}
		//开始数组拷贝
		memcpy(arr+left,p,sizeof(int)*(end-st+1));
		delete [] p;
		//防止内存泄露。

}

桶排序

桶排序又叫计数排序,当被排列的数字集合是有限个时(n),我们可以划分(n)个桶,然后遍历每一个元素,遍历到的时候对应元素所属的桶子计数加一,注意,桶子是有序摆放的,所以遍历玩整个数组之后,按照桶子的顺序输出即可。
时间复杂度0(n),空间复杂度根据元素集合的个数而定。比如以下两种情况:
1,

3 2 3 4 5 6 7 8 6 5 6 8 9

元素是一个有限的离散的集合,且数组元素的个数远远大于集合中元素的个数,此时空间复杂度小,采用桶排序即可
2,

3.453  4.33 5.342   7.3432 6.7645

当给浮点数进行排序的时候,因为浮点数是一个范围数,连续区间,而不是离散的空间,因此不适合桶排序
3,

 1  456  1890 2987634

第三种情况,当数组元素范围远远大于数组长度的时候,需要建立的桶子的个数也远远要大于数组的长度,此时不适合桶排序,这里只有四个元素,采用简单的交换式排序方法即可。
代码

void BucketSort(int arr[],int L)
{
	//首先构造一个10桶,每一个桶里面的元素都是初始化为零,而且下标表示元素序号
	int bucket[10] = {0,0,0,0,0,0,0,0,0,0};
	for (int i = 0; i < L; i++)
	{
		bucket[arr[i]]++;
	}
	int k = 0;
	for (int i = 0; i < 10; i++)
	{
		//这里面的元素值就是i
		for (int j = 0; j < bucket[i]; j++)
		{
			arr[k++] = i;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值