常见排序算法

假定一数组 arr[] = { 9,8,7,6,5,4,3,2,1 } 需改为升序排列。

一、插入排序 

我们可以将这个数组分为两个部分--已排序的部分和未排序的部分,而第一个元素是已经排好的。

然后再用第一个未被排序的元素n与已经排序的最后一个元素m进行比较,如果n<m,就与m的前一个数进行比较,同时将这些元素向后移,直到遇到一个小于n的数或是达到了下标为0的位置,再将这个数存下。

 之后重复这个过程直到遍历完这个数组。

时间复杂度O(N^2)
空间复杂度O(1)
稳定性稳定

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

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

		}
		arr[end + 1] = tmp;
	}
}

二 、希尔排序

因为直接排序的元素集合越接近有序,插入排序的时间效率越高,因此希尔排序就是先让集合尽可能的接近有序,再进行一个插入排序。

我们还是将数组 arr[] = { 9,8,7,6,5,4,3,2,1 } 改为升序排列。

首先确定一个gap的值(这里取gap=3),让每隔gap-1的数进行比较并进行交换来进行一个预排序。这时再进行插入排序,就会快的多了。

但是如果数组中的较多的时候,比如数组中含有1亿个数,那么只交换一次再进行插入排序,并不能比插入排序快多少,因此我们需要调整gap的值。

因为gap的值越大,数组中大的值越快排到后面,小的数越快到前面,但是不接近有序。而gap的值越小,越接近有序,当gap等于1时,就是插入排序。因此我们可以取gap等于数组长度n或n/2等,并且在预排序时逐渐的减小gap的值,直到gap=1,就可以完成集合的排序。

时间复杂度O(n^{1.25})O(1.6*n^{1.25})
稳定性不稳定

相同的值在预排时有可能分到不同组里,所以不稳定。 

void ShellSort(int*arr,int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//防止gap为0,且最后一次执行时gap一定为1
		for (int i = 0; i < n - gap; i++)//最后gap个会在end+gap时被比较
		{
			int end = i;
			int tmp = arr[i + gap];
			while (end >= 0)
			{
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
					end=end-gap;
				}
				else
					break;

			}
			arr[end + gap] = tmp;
		}
	}
}

三、堆排序

http://t.csdn.cn/4hz7l

四、选择排序

我们每遍历一遍数组就能找到数组中的最大值和最小值,我们再将这两个值放在头和尾就完成了一次排序,之后再次遍历剩下的数组,重复这个过程直到完成排序。

时间复杂度O(N^2)
空间复杂度 O(1)
稳定性不稳定

选择排序是不稳定的!

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		int maxi = left;
		int mini = left;
		for (int i = left+1; i <= right; i++)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		swap(&a[mini], &a[left]);
		if (left == maxi)//此时maxi为0
		{
			maxi = mini;//防止left和mini相等时,maxi被换完后又被换回去
		}
		swap(&a[maxi], &a[right]);
		left++;
		right--;
	}
}

五、冒泡排序

老熟人冒泡排序想必大家都很熟悉,就是通过每个数的相互比较,使每个数找到正确的位置,从而完成排序。

时间复杂度O(N^2)
空间复杂度 O(1)
稳定性稳定

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int exchange = 0;
		for (int j = 0; j < n - i - 1; j++)
		{
			if (a[j] > a[j+1])
			{
				Swap(&a[j+1], &a[j]);
				exchange = 1;
			}
		}
		if (exchange == 0)//如果为0表示没有进行过交换,即已经进行完排序
			break;
	}
}

六、快速排序

1.hoare版

这里我们用arr[]={5,3,2,6,7,1,9,8,4}这个数组。

我们首先选定一个值key进行排序,从左右两侧寻找比key小和比key大的数并交换,最后再将key与left和right相交时的点交换,使key左侧的值都比key要小,右侧的值都比key大。

之后再通过递归对key两侧的数进行排序。

void PartSort1(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int keyi = left;//keyi为下标
	while (left < right)
	{
		while (left < right && a[right]>=a[keyi])//让右先走,保证相遇的位置比key对应的数要小
			//要是相遇的位置比key对应数要大的话,交换完后key的左侧就不全是比key对应数要小的数了
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		swap(&a[left], &a[right]);
	}
	swap(&a[left], &a[keyi]);
	keyi = left;
	PartSort1( a, begin, keyi - 1);
	PartSort1(a, keyi+1, end);
}

2.挖坑法

我们还是先选取一个key值,在从右向左找比key要小的值,不过这次需要把找到的值赋给key所在的位置。这样我们可以理解为把a[right]的值赋给a[keyi]后,a[right]被挖去。

之后再从左寻找比key要大的值 ,将a[left]值赋给a[right],同时再将a[left]挖去。最后将key的值放在a[left]位置上,重复这个过程直到left=right,再将key的值放于此处。

之后再通过递归对left(right)两侧进行排序,使得各元素达到正确位置。

void PartSort2(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int key = a[left];//需要保存被挖掉的值
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[left] = a[right];
		//找到一个比key(a[left])要小的值,并把这个值放在a[left]的位置

		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[right] = a[left];
		//把找到的值放在刚才a[right]的空位上
	}
	a[left] = key;
	PartSort2(a, begin, left-1);
	PartSort2(a, left + 1, end);
}

3.前后指针法

确立key后,使prev=left,cur=left+1,之后++cur直到遇到比key小的值再++prev,交换a[prev]与a[cur]的值,遍历完后再交换a[prev]和a[keyi]的值从而完成一次排序。之后再递归排完key两侧的数。

4.非递归方式(栈实现)

 通过观察第一种方法我们可以发现,每进行一次排序就会使key两侧的数变成都比key大或是小的数,之后就是通过递归来重复这个过程,因此我们只需要递归的实现即可。每一次递归的参数都是key一侧的头和尾,所以我们可以用栈来保存这两个值,需要递归时就取出栈顶的两个值,这两个值就可以作为参数完成一次排序。

int PartSort(int* a, int begin, int end)
{
	int left = begin;
	int right = end;
	int key = a[left];
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[left] = a[right];
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[right] = a[left];
	}
	a[left] = key;
	return left;
}

void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);
	while (!StackEmpty(&st))
	{
		int left;
		int right;
		right = StackTop(&st);
		StackPop(&st);

		left = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort(a, left, right);//进行一次排序
		if (left < keyi-1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi-1);
		}
		if (keyi+1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}
	}
}

 5.优化

假设需要排的数组是arr={1,2,3,4,5,6,7,8,9},这样一个已经被排好的数组,就会使时间复杂度达到了O(N^2)。 因此我们不能一直将最左侧或最右侧的值作为key,而是应该根据不同的情况更改key值。

(1)三数取中

取最左值、最右值和中间数三者中不是最大也不是最小的值,然后再将它与最左侧或最右侧的值进行交换。

int GetMidIndex(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;//防止left+right的值超出int大小
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

(2) 小区间插入

如果数组的子区间较小,可以采用插入排序的方法,因此在排序前可以判断一下。

void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1  <= 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort2(a, begin, end);
		QuickSort2(a, begin, keyi - 1);
		QuickSort2(a, keyi + 1, end);
	}
}
时间复杂度O(N*logN)
空间复杂度O(logN)
稳定性不稳定

七、归并排序

1.递归方法

归并排序是通过比较begin1和begin2所指对象的大小来完成排序的,但是如果begin1和begin2的数组并不有序排出来的数组也不是有序的,所以可以将其分治成小区间来进行比较完成,从而完成小区间内的排序。

我们再将较小值尾插入一个临时数组,完成排序时再将临时数组的值赋值给原数组。

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;
	int mid = (left+right) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	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++];

	for (int j = left; j <= right; j++)//把排好的数放回去
	{
		a[j] = tmp[j];
	}
}

void MergeSort(int* a, int n)
{
	int*tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a,0,n-1,tmp);
	
	free(tmp);
}

 2.非递归方法

void _MergeSortNonR(int* a, int begin1, int end1,int begin2,int end2, int* tmp)
{
	int j = begin1;//保存begin1的值,防止被更改后找不到
	int i = begin1;
	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++];

	for (j; j <= end2; j++)
	{
		a[j] = tmp[j];
	}
}


void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i = 2*gap+i)
		{
			int begin1 = i, end1 = i + gap - 1, begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (begin2 >= n)//最后一个小组的第一个小区间不够gap个就跳出,在更大的区间来排
				break;

			if (end2 >= n)//最后一个小组的最后一个小区间不够gap或是根本没有,就不需要归并了
				end2 = n - 1;

			_MergeSortNonR(a,begin1,end1,begin2,end2,tmp);
		}
		gap *= 2;
	}
	free(tmp);
}
时间复杂度O(N*logN)
空间复杂度O(N)
稳定性稳定

八、非比较排序 

首先创建一个size大小的空间,size等于最大值减最小值。每有一个相同的数字就使该数字减去最小值作为下标,使其加1。然后根据创建数组中的值重新赋值给原数组即可完成排序。

void CountSort(int* a,int n)
{
	int max = a[0];
	int min = a[0];
	for (int i = 1; i < n; i++)
	{
		if (max < a[i])
			max = a[i];
		if (min > a[i])
			min = a[i];
	}
	int range = max - min + 1;
	int* count = (int*)calloc(n, sizeof(int) * range);
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	int i = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}
}

 局限性:只适合一组数,只适合整数,且数据越集中效率越高。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值