排序(冒泡,插入,希尔,归并,快排,选择,堆排序,桶排序,基数排序)

稳定的排序 时间复杂度 空间复杂度
冒泡排序 最差、平均O(n^2),最好O(N) 1
鸡尾酒排序(双向冒泡排序) 最差、平均O(n^2),最好O(N) 1
插入排序 最差、平均O(n^2),最好O(N) 1
归并排序 最差、平均、最好O(nlogn) n
桶排序 n k
基数排序 dn n
二叉树排序 nlogn n
图书馆排序 nlogn (1+e)n
不稳定的排序 时间复杂度 空间复杂度
选择排序 最差、平均n^2 1
希尔排序 nlogn 1
堆排序 最差、平均、最好O(nlogn) 1
快速排序 平均nlogn,最坏n^2 logn

快速排序

分而治之,快排最好的情况,选主元每次正好中分,T(N) = O(NLogN),最坏情况,O(n*n)

 1.选主元

取头、中、尾的中位数

//选主元,取头、中、尾的中位数
int median3(int A[], int left, int right)
{
	int center = (left + right) / 2;
	if(A[left] > A[center])
		swap(&A[left], &A[center]);
	if(A[left] > A[right])
		swap(&A[left], &A[right]);
	if(A[center] > A[right])
		swap(&A[center], &A[right]);
	//交换后,A[left]<=A[center]<=A[right]
	swap(&A[center], &A[right - 1]);//将pivot藏到右边
	//只需考虑A[left+1] ... A[right-2]

	return A[right - 1];
}

2.子集划分

特殊情况:如果有元素正好等于pivot,停下来交换。这样的话可以尽可能将两边的子集划分的均衡

 在小规模数据直接调用简单排序,大规模数据就用快排

快排的过程如下:

快排就是在一趟排序后,主元到了它该去的位置,即,它左边的数都是比它小的,右边的数都是比它大的

#include<iostream>

using namespace std;

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

//选主元,取头、中、尾的中位数
int median3(int A[], int left, int right)
{
	int center = (left + right) / 2;
	if(A[left] > A[center])
		swap(&A[left], &A[center]);
	if(A[left] > A[right])
		swap(&A[left], &A[right]);
	if(A[center] > A[right])
		swap(&A[center], &A[right]);
	//交换后,A[left]<=A[center]<=A[right]
	swap(&A[center], &A[right - 1]);//将pivot藏到右边
	//只需考虑A[left+1] ... A[right-2]

	return A[right - 1];
}

void quick_sort(int A[], int left, int right)
{
	if(left >= right)
		return;

	int pivot = median3(A, left, right);	
	int i = left;
	int j = right - 1;
	for(;;)
	{
		while(i<right-1 && A[++i] < pivot){//注意i的范围
		}

		while(j>-1 && A[--j] > pivot){//注意j的范围,之前就就是因为i,j的范围没限定,导致一直有错
		}

		if(i < j)
		{
			swap(&A[i], &A[j]);
		}
		else
		{
			break;
		}		
	}
	
	swap(&A[i], &A[right - 1]);

	quick_sort(A, left, i-1 );
	quick_sort(A, i+1 , right);
}

int main()
{
	int n;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];


	quick_sort(A, 0, n-1);
	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}


冒泡排序

一趟排序后,将最大的元素放到了最后,然后再对最大元素之前的进行同样的操作

最好情况,顺序,T=O(N),最坏情况,逆序,T=O(N*N)

因为冒泡排序中,在前一个元素严格大于后一个元素时才交换,所以他是稳定的。

#include<iostream>
using namespace std;

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

void bubble_sort(int A[], int n)
{
	for(int p = n - 1; p >= 0; p--)
	{
		int flag = 0;//用一个标记,如果没改变的话,说明已经是排好序的,就不用再循环
		for(int i = 0; i < p; ++i)
		{
			if(A[i]>A[i + 1])
			{
				swap(&A[i], &A[i + 1]);
				flag = 1;
			}
		}
		if(0 == flag)
			break;	
	}
}

int main()
{
	int n;
	cout << "冒泡排序: " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	bubble_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

插入排序

把插入排序想象成摸扑克牌的过程,手里首先有一张牌,A【0】,然后从A[1]开始摸,如果比前一张小,前一张就后移,直到遇到合适的位置,再将这张牌放入

最好情况,顺序,T=O(N),最坏情况,逆序,T=O(N*N)

因为插入排序中,在严格大于时才后移,所以他是稳定的。

#include<iostream>

using namespace std;

void insertion_sort(int A[], int n)
{
	for(int p = 1; p < n; p++)
	{
		int tmp = A[p];//现在摸到的牌,要将他放到它该在的位置
		int i = p;
		for(; i>0 && A[i - 1] > tmp; i--)
		{
			A[i] = A[i - 1];
		}
		A[i] = tmp;
	}
}

int main()
{
	int n;
	cout << "插入排序 " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	insertion_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

希尔排序

希尔排序和插入排序有点像,不同在于插入时比较的数字之间的间隔不一样

比如:81  94  11  96  12   35  17  95  28  58  41  75  15  

先每隔5间隔的数做插入排序,那么就是 81  35  41,他们就成了 35 41 81,在数组中就是:

35  94  11  96  12   41  17  95  28  58  81  75  15  

再对下一组“5-间隔”数 94 17 75

35  17  11  96  12   41  75  95  28  58  81  94  15  

接下来同理,结束后,从【5-间隔】变成【3-间隔】,最后是【1-间隔】

增量序列,Dm > Dm-1 > ... > D1 =1

对每个Dk进行【Dk-间隔】的排序,Dk间隔有序,Dk-1间隔排序后,不会把Dk间隔排好的序给打乱,

原始希尔排序Dm = N/2 (取下界),Dk = (Dk+1) /2,(取下界),最坏时间T = (N^2)

缺陷:增量元素不互质,小增量可能不起作用。

改进:

Hibbard增量序列:Dk = 2^k -1,相邻元素互质,最坏T = (n^1.5),平均时间O(n^1.25)

Sedgewick增量序列:{1,5,9,41,109,。。。} 9* 4^i - 9*2^i + 1 或 4^i - 3*2^i + 1,平均时间(n^7/6),最坏时间O(n^4/3)

#include<iostream>

using namespace std;

void shell_sort(int A[], int n)
{
	for(int D = n / 2; D > 0; D = D / 2)//增量序列
	{
		for(int p = D; p < n; ++p)//插入排序
		{
			int tmp = A[p];
			int i = p;
			for(; i >= D && A[i - D]>tmp; i = i - D)//和插入排序不同的地方就是换成了D
			{
				A[i] = A[i - D];
			}
			A[i] = tmp;
		}
	}
}

int main()
{
	int n;
	cout << "希尔排序 " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	shell_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

归并排序(递归和非递归)

归并排序适用于外排序,它要另外开辟一个空间作为临时的存储,核心在于两个有序子序列的合并。

递归:

#include<iostream>

using namespace std;

//核心,有序子列的归并
void merge(int A[], int tmpA[], int l, int r, int rightend)
{
	int leftend = r - 1;
	int tmp = l;
	int nums = rightend - l + 1;//元素个数
	while(l <= leftend && r <= rightend)
	{
		if(A[l] <= A[r])
			tmpA[tmp++] = A[l++];
		else
			tmpA[tmp++] = A[r++];
	}
	while(l <= leftend)
		tmpA[tmp++] = A[l++];
	while(r <= rightend)
		tmpA[tmp++] = A[r++];

	//把元素拷回A数组
	for(int i = 0; i < nums; i++, rightend--)
	{
		A[rightend] = tmpA[rightend];
	}
}

//递归
void Msort(int A[], int tmpA[], int l, int rightend)
{
	int center;
	if(l < rightend){
		center = (l + rightend) / 2;
		Msort(A, tmpA, l, center);
		Msort(A, tmpA, center+1, rightend);
		merge(A, tmpA, l, center + 1, rightend);
	}
}
void merge_sort(int A[], int n)
{
	int *tmpA;
	tmpA =(int*)malloc(n* sizeof(int));
	if(tmpA != nullptr)
	{
		Msort(A, tmpA, 0, n - 1);
		free(tmpA);
	}

}

int main()
{
	int n;
	cout << "归并排序(递归) " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	merge_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

非递归:

#include<iostream>
using namespace std;
//和merge不同在不用从tmpA拷回A
void merge1(int A[], int tmpA[], int l, int r, int rightend)
{
	int leftend = r - 1;
	int tmp = l;
	int nums = rightend - l + 1;//元素个数
	while(l <= leftend && r <= rightend)
	{
		if(A[l] <= A[r])
			tmpA[tmp++] = A[l++];
		else
			tmpA[tmp++] = A[r++];
	}
	while(l <= leftend)
		tmpA[tmp++] = A[l++];
	while(r <= rightend)
		tmpA[tmp++] = A[r++];
}

void merge_pass(int A[], int tmpA[], int n, int length)//length,当前子序列的长度
{
	int i = 0;
	for(; i <= n - 2 * length; i = i + 2 * length)
		merge1(A, tmpA, i, i + length, i + 2 * length - 1);//A元素归并到tmpA后,不拷回A

	if(i + length < n)//归并最后两个序列
		merge1(A, tmpA, i, i + length, n - 1);
	else//只剩一个序列
	{
		for(int j = i; j < n; ++j)
			tmpA[j] = A[j];
	}
}

void merge_sort(int A[], int n)
{
	int length = 1;
	int *tmpA;
	tmpA = (int*)malloc(n*sizeof(int));
	if(tmpA != nullptr)
	{
		while(length < n)
		{
			merge_pass(A, tmpA, n, length);
			length *= 2;
			merge_pass(tmpA, A, n, length);//刚好归并回A
			length *= 2;
		}
		free(tmpA);
	}

}
int main()
{
	int n;
	cout << "归并排序(非递归) " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	merge_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

选择排序

#include<iostream>
using namespace std;

void select_sort(int A[], int n)
{
	
	for(int i = 0; i < n-1; ++i)
	{
		int min_index = i;
		for(int j = i + 1; j < n; ++j)
		{
			if(A[j] < A[min_index])
				min_index = j;
		}
		//从未排序的部分中找出值最小的,然后通过交换放到了有序部分的末尾
		int temp = A[i];
		A[i] = A[min_index];
		A[min_index] = temp;
	}
}

int main()
{
	int n;
	cout << "选择排序 " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	select_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

堆排序

首先将数组建成一个最大堆,然后将堆顶和数组末尾交换,使最大的元素到末尾,再对剩下的部分调整成最大堆,再把堆顶交换到末尾,重复这个过程。

#include<iostream>
using namespace std;

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

void adjustheap(int A[], int parent, int n)
{
	int child;
	int temp = A[parent];//当前要调整的元素
	//当前要调整的元素要和他的孩子相比较,但是不能越界,所以就有如下的循环条件
	for(; parent*2+1 < n; parent = child)
	{
		child = parent * 2+1;
		//如果有右孩子且右孩子的值大于左孩子
		if((child +1 < n) && (A[child]) < A[child + 1])
			child++;//找到了左右孩子中更大的
		//如果temp更大,那么现在这个位置他是可以“坐稳”的,跳出,否则就将更大的孩子提上来
		if(temp >= A[child])
			break;
		else
			A[parent] = A[child];
		//一趟结束后,parent就去到更大的孩子的下标的位置
	}
	A[parent] = temp;
}

void heap_sort(int A[], int n)
{
	for(int i = n / 2 - 1; i >= 0; i--)//从最后一个有孩子的结点开始调整,调整出一个最大堆
	{
		adjustheap(A, i, n);
	}
	//调整完之后,根结点就是最大值,将最大值交换到数组的最后,然后对剩下的元素又重新调整成最大堆,重复这个过程
	for(int i = n - 1; i >= 1; --i)
	{
		swap(&A[0], &A[i]);
		adjustheap(A, 0, i);
	}
}

int main()
{
	int n;
	cout << "堆排序 " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	heap_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

桶排序

桶排序需要知道输入数据的范围。假设N个学生,成绩0~100,所以建立101个桶,M=101。扫描一遍所有学生的成绩,将它放到对应的桶中。再扫面一遍 桶,输出结果。时间O(M+N)。

#include<iostream>

using namespace std;

//假如是成绩0~100,所以有101个桶
void bucket_sort(int A[], int n)
{
	int grade[101] = { 0 };
	for(int i = 0; i < n; ++i)
	{
		grade[A[i]]++;
	}
	for(int i = 0; i <= 100; ++i)
	{
		while(grade[i])
		{			
			cout << i << " ";
			grade[i]--;
		}

	}
}

int main()
{
	int n;
	cout << "桶排序 " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	bucket_sort(A, n);
	

	delete[] A;
	return 0;
}

基数排序

当数据范围很大的时候,桶排序就不合适,所以用基数排序。基数排序分为最低位优先和最高位优先,以下代码用最低优先,即,先根据所有数的个位数排序,再对排了序的数根据十位数排序,再对排了序的数根据百位的数值大小排序……

以下代码基数为十进制,最大为三位数

#include<iostream>
using namespace std;

//取一个数的个位,或者十位,或者百位……
//d=1,表示取个位,d=2取十位
int get_digit(int num, int d)
{
	int val;
	while(d--)
	{
		val = num % 10;
		num = num / 10;
	}
	return val;
}

void radix_sort(int A[], int n)
{
	int radix = 10;//十进制
	int *count = new int[radix];//存放对应数位是0,1,2,3...的数的个数
	int *bucket = new int[n];//一个桶来存放A数组的数,用于中转

	for(int d = 1; d <= 3; ++d)//最大的数有多少位,就要循环多少次,假设最大的数在这里是3位数,所以外层循环3次
	{
		//初始化为0
		for(int i = 0; i < radix; ++i)
			count[i] = 0;
		//统计数位是0,1,2,3...的数分别有多少个
		for(int i = 0; i < n; ++i)
		{
			int j = get_digit(A[i], d);
			count[j]++;
		}
		//意思是,数位为i的数,在它的前面最多有多少个数
		for(int i = 1; i < radix; ++i)
			count[i] = count[i] + count[i - 1];

		//将数组中的数从后往前装入桶中,保证了稳定性
		for(int i = n - 1; i >= 0; --i)
		{
			int j = get_digit(A[i], d);
			bucket[count[j] - 1] = A[i];//根据这个数的数位上是多少,就放到桶的对应的位置
			count[j]--;
		}

		//把桶中的数放回A中,完成一趟排序,下一趟就是比较A数组各个数的十位/百位
		for(int i = 0; i < n; ++i)
		{
			A[i] = bucket[i];
		}
	}
	delete[] count;
	delete[] bucket;
}

int main()
{
	int n;
	cout << "基数排序 " << endl;
	cout << "输入元素个数: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	radix_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i]<<"  ";
	cout << endl;

	delete[] A;

	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_22080999/article/details/81060103