常见排序算法总结

我们学习过很多排序算法,但是我们又很难将它们记住,当面试的时候问到其中的一个排序算法的时候,或者需要解决某些问题的时候,我们需要选择一些排序算法。如果我们此时不熟悉常见排序的特点,那么我们真的会很难受的,我感觉我就经历过这种事。所以我觉得我有必要重新认识一下常见排序算法并将它们总结一下。好,闲话不扯了,我们开始吧。


1.冒泡排序

代码:

void BubbleSort(int* arr, int size)
{
	assert(arr && size > 0);
	for (int i = 0; i < size - 1; ++i)
	{
		int flag = 0;  //用于优化该排序
		for (int j = 0; j < size - i - 1; ++j)
		{
			if (arr[j + 1] < arr[j])
			{
				swap(arr[j + 1], arr[j]);
				flag = 1;
			}
		}
		if (flag == 0)
			return;
	}
}

---时间复杂度 O(n^2),没有消耗空间

---请仔细看看以上代码,看看边界的控制,自己理解一下吧,这里我写的是优化版的。


2.插入排序

代码:

void InsertSort(int* arr, int size)
{
	assert(arr && size > 0);
	for (int i = 1; i < size; ++i)
	{
		int tmp = arr[i];  //暂时存放该值
		int j = i-1;
		while (j >= 0 && arr[j] >= tmp)
		{
			arr[j + 1] = arr[j];
			--j;
		}
		arr[j+1] = tmp;
	}
}

---时间复杂度 O(n^2),没有消耗空间
---这个排序也要控制好结束条件,没控制好,可能就会越界。


3.选择排序

代码:

void SelectSort(int *arr,int size)
{
	assert(arr && size > 0);
	int left = 0;
	int right = size - 1;
	while (left < right)
	{
		int max = left;
		int min = left;
		for (int i = left + 1; i <= right; ++i)
		{
			if (arr[i]>arr[max])
				max = i;
			if (arr[i] < arr[min])
				min = i;
		}

		if (min == right && max == left)  //此种情况交换两次会导致错误(依次递减)
		{
			swap(arr[min], arr[max]);
			++left;
			--right;
			continue;
		}

		if (min != left)
		{
			swap(arr[left], arr[min]);
		}
		
		if (max != right)
		{
			swap(arr[right], arr[max]);
		}
		
		++left;
		--right;
	}
}
---时间复杂度 O(n^2),没有消耗空间

---这个排序可以每次找出最大和最小的两个数,这样遍历次数减小一半,不过这样需要注意一种特殊情况就是,上面我的代码中写了注释的地方,其他的则不易出错


4.堆排序

代码:

void AdjustDown(int* arr, int root, int size)
{
	int begin = root;
	int left = 2 * root + 1;
	int right = left + 1;
	while (left < size)
	{
		int max = left;
		if (right<size && arr[right]>arr[max])
		{
			max = right;
		}

		if (arr[begin] < arr[max])  //根比叶子小,则交换
		{
			swap(arr[begin], arr[max]);
		}
		else  //否则,退出
		{
			break;
		}

		begin = max;
		left = begin * 2 + 1;
		right = left + 1;
	}
}
void HeapSort(int* arr, int size)
{
	assert(arr && size > 0);
	
	//建堆
	int root = size / 2 - 1;
	while (root >= 0)
	{
		AdjustDown(arr, root, size);
		--root;
	}

	//排序
	int begin = size-1; 
	while (begin)
	{
		swap(arr[0], arr[begin]);
		AdjustDown(arr, 0, begin);
		--begin;
	}
}

---时间复杂度 O( n(lg N) ),没有消耗空间
---堆排序我个人觉得比较重要,需要好好掌握以下,因为它的用处很特别。

  (1). 建堆,(其数组看成完全二叉树),从最后一个"根节点"起,依次向下调整,调整完以后最大堆或最小堆就建立起来了

  (2).每次将筛选出来的最大值或最小值放到数组最后一位(其实是将数组第一位与最后一位交换),然后将堆的规模减小1,当规模为1的时候,数组里面就是排好序的了。

  (3). 其实堆排序最重要的是用来找出k个最小数或k个最大数。思路:简历堆,然后遍历数组,置换出堆中的数据,遍历完的时候堆中的数据就是索要求的结果了。至于创建最大堆还是最小堆,这有个技巧,就是与你所求的相,即:找最小用打大堆,找最大用小堆。最后注意堆的使用场合。


5.希尔排序

代码:

void ShellSort(int * arr, int size)
{
	assert(arr && size > 1);
	int gap = size;
	while (gap > 1)
	{
		gap = gap / 3 + 1;  //选择gap
		for (int i = 0; i < gap; ++i)
		{
			for (int j = i+gap; j < size; j = j + gap) //以gap为间隔插入排序
			{
				int tmp = arr[j];
				int k = j - gap;
				while (k>=0 && arr[k]>tmp)
				{
					arr[k + gap] = arr[k];
					k -= gap;
				}
				arr[k + gap] = tmp;
			}
		}
	}
}
---时间复杂度不确定,据统计在O(n^1.25)~O(1.6n^1.25)之间,没有消耗空间

---希尔排序的思想是缩小增量排序,注意gap的取法对时间复杂度有影响,像上面这样取gap是比较好的做法


*6.快速排序
代码:

void QuickSort1(int* arr, int size) //一般方式
{
	assert(arr);
	if (size <= 1)
	{
		return;
	}

	int key = arr[size - 1]; //以最后一个元素为key值
	int begin = 0;
	int end = size - 2;
	while (begin < end)
	{
		while (begin<end && arr[begin] <= key)  //找大
		{
			++begin;
		}
		while (end>begin && arr[end] >= key)	 //找小
		{
			--end;
		}
		
		if (arr[begin] > arr[end])
		{
			swap(arr[begin], arr[end]);
			++begin;
			--end;
		}
	}

	if (begin==end && arr[begin] < key)  //避免逆序时出错
	{
		begin++;
	}

	if (begin != size - 1)  //若它和最后一个重合了,就不交换了
	{
		swap(arr[begin], arr[size - 1]);
	}

	QuickSort1(arr, begin);  //左边递归排序,begin此时代表数组元素的个数
	QuickSort1(arr + begin + 1, size - begin - 1); //右边递归排序
}

int MidValue(int* arr, int size)
{
	if (size == 1)
		return 0;

	int left = arr[0];
	int right = arr[size - 1];
	int mid = arr[(size - 1) / 2];

	if (left > right)
	{
		if (mid>left)
			return 0;
		else
		{
			if (mid < right)
				return size-1;
			else
				return (size-1)/2;
		}
	}
	else
	{
		if (mid>right)
			return (size - 1);
		else
		{
			if (mid < left)
				return 0;
			else
				return (size - 1) / 2;
		}
	}
}
void QuickSort2(int* arr, int size) //三数取中法
{
	assert(arr);
	if (size <= 1)
		return;
	int pos = MidValue(arr, size);
	swap(arr[pos], arr[size - 1]);
	int key = arr[size - 1];

	int begin = 0;
	int end = size - 2;
	while (begin < end)
	{
		while (begin<end && arr[begin] <= key)
		{
			++begin;
		}
		while (end>begin && arr[end] >= key)
		{
			--end;
		}

		if (arr[begin] > arr[end])
		{
			swap(arr[begin], arr[end]);
			++begin;
			--end;
		}
	}

	if (begin == end && arr[begin] < key)
	{
		++begin;
	}
	if (begin != size-1)
		swap(arr[begin], arr[size-1]);
	

	QuickSort2(arr, begin); //begin此时代表数组元素的数目,左边递归排序
	QuickSort2(arr + begin + 1, size - begin - 1); //右边递归排序
}

void QuickSort3(int* arr, int size) //非递归实现快速排序
{
	assert(arr);
	if (size <= 1)
		return;
	stack<int> sk;

	sk.push(0); 
	sk.push(size-1);
	//压栈的是数组的下标,与上面的不同,需要注意
	while (!sk.empty())
	{
		int last = sk.top();
		sk.pop();
		int first = sk.top();
		sk.pop();
		int begin = first;
		int end = last-1;
		int key = arr[end+1];
		while (begin < end)
		{
			while (begin < end && arr[begin] <= key)
			{
				++begin;
			}
			while (end>begin && arr[end] >= key)
			{
				--end;
			}
			
			if (arr[begin] > arr[end])
				swap(arr[begin], arr[end]);
		}
		if (begin == end && arr[begin] < key)
			begin++;
		if (begin != end + 1)
			swap(arr[begin], arr[last]);
		
		int begin1 = first;
		int end1 = begin;
		if (end1 - begin1 > 1)
		{
			sk.push(begin1);
			sk.push(end1-1); //此时压的是下标
		}

		int begin2 = begin + 1;
		int end2 = last;
		if (end2 - begin2 > 1)
		{
			sk.push(begin2);
			sk.push(end2); //此时压的是下标
		}
	}
}

void  QuickSort4(int* arr, int size) //可用于数组和链表的快排
{
	assert(arr);
	if (size <= 1)
		return;

	int pos = MidValue(arr, size); //size表示元素个数
	swap(arr[pos], arr[size - 1]);
	int key = arr[size - 1];

	int prev = -1;
	int cur = 0;
	while (cur < size)
	{
		if (arr[cur] < key)
		{
			++prev;
			if (prev != cur)
				swap(arr[prev], arr[cur]);
		}
		++cur;
	}

	swap(arr[++prev],arr[size-1]);

	QuickSort4(arr,prev);
	QuickSort4(arr+prev+1,size-prev-1);
}

void QuickSort5(int* arr, int size)  //按区间排序,小于13和大于13
{
	assert(arr);
	if (size <= 1)
		return;

	if (size < 13)
	{//插入排序
		InsertSort(arr, size);
	}
	else
	{//快速排序
		QuickSort1(arr, size);
	}
}
---时间复杂度O(n(lg N) ),没有消耗空间

---快排算的上是最有名的排序算法,上面写了四种快速排序,可见它的重要性以及复杂性,可以这样会说快排就像开车一样,不会开的人开再好的也没用,会的人能充分的利用它。下面我就简单谈谈吧

  (1). 快速排序的key值的选择,选择太小或太大都会使整体偏低,所以我给出了三数取中法。

  (2). 快速排序的区间选择很重要,区间大于13适合使用快排,区间小于13使用插入排序,这样的效率比较高(是经过测试得出的)

  (3). 快速排序还能用于链表的排序,这点很重要


7.归并排序

代码:

void doMerge(int* newArr, int left, int right)
{
	assert(newArr);
	if (left == right)
		return;
	int mid = left + (right - left) / 2;
	doMerge(newArr, left, mid);
	doMerge(newArr, mid + 1, right);
	for (int i = left + 1; i <= right; ++i)
	{
		int tmp = newArr[i];
		int end = i-1;
		while (end >= left && newArr[end] > tmp)
		{
			newArr[end+1] = newArr[end];
			--end;
		}
		newArr[end+1] = tmp;
	}
}
void Merge(int* arr, int* newArr, int left, int mid, int right)
{
	assert(arr);
	assert(newArr);
	if (left == right)
		return;
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int pos = 0;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (newArr[begin1] <= newArr[begin2])
		{
			arr[pos++] = newArr[begin1++];
		}
		else
		{
			arr[pos++] = newArr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		arr[pos++] = newArr[begin1++];
	}
	while (begin2 <= end2)
	{
		arr[pos++] = newArr[begin2++];
	}
}
void MergeSort(int* arr, int size)
{
	assert(arr);
	if (size <= 1)
		return;

	int left = 0;
	int right = size - 1;
	int mid = (size-1) / 2;

	int * newArr = new int[size];
	memcpy(newArr, arr, sizeof(int)*size);
	assert(newArr);

	doMerge(newArr, left, mid);
	doMerge(newArr, mid + 1, right);
	Merge(arr, newArr, left, mid, right);
}
---时间复杂度O(n(lg N) ),空间复杂度O(n)

---归并排序是一种空间换取时间的排序,我们需要理解归并排序的思想,即分治的思想



8.计数排序

代码:

void CountingSort(int* arr, int size)  //计数排序
{//数据集中的的时候很好
	assert(arr);
	if (size <= 1)
		return;

	int min = arr[0];
	int max = 0[arr];

	for (int i = 1; i < size; ++i)
	{
		if (arr[i]>max)
			max = arr[i];
		if (arr[i] < min)
			min = arr[i];
	}
	//找打区间
	int arrSize = max - min + 1;
	int* newArr = new int[arrSize];
	memset(newArr, 0, sizeof(int)*arrSize);
	for (int i = 0; i < size; ++i)
	{
		++newArr[arr[i] - min];
	}

	for (int j = 0 , i=0; j < arrSize; ++j)
	{
		while (newArr[j] != 0)
		{
			arr[i++] = j + min;
			--newArr[j];
		}
	}
}
---时间复杂度O(n),空间复杂度O(n)

---这种排序是一种典型的空间换取时间的排序,若数字的分布区间较小,非常合适,因为额外开辟的数组就是数字区间的大小,所以当数字的分布区间较大的时候就不适合了



9.基数排序

代码:

int digData(int data, int dig)
{
	while (--dig)
	{
		data /= 10;
	}
	return data % 10;
}
void radixSort(int* arr, int left, int  right, int dig)
{
	if (left>=right || dig < 1)
		return;

	int* count = new int[10];
	int* newArr = new int[right - left + 1];
	memset(count, 0, sizeof(int)* 10);
	memset(newArr, 0, sizeof(int)* (right - left + 1));

	for (int i = left; i <= right; ++i) //统计每位的元素的个数
	{
		++count[digData(arr[i], dig)];  
	}
	for (int i = 1; i < 10; ++i) //记录新的以为元素的开始地址
	{
		count[i] = count[i] + count[i - 1];
	}
	for (int i = left; i <= right; ++i)  //将排序后的元素写入辅助数组newArr中
	{
		int pos = digData(arr[i], dig);
		newArr[--count[pos]] = arr[i];
	}
	int j = 0;
	for (int i = left; i <= right; ++i, ++j) //将排序好的元素写回arr中
	{
		arr[i] = newArr[j];
	}
	
	for (int i = 0; i < 10; i++)
	{
		int first = count[i];
		int last = count[i + 1] - 1;
		radixSort(arr, first, last, dig - 1);
	}
}

void RadixSort(int* arr, int size,int dig)
{
	assert(arr);
	if (size <= 1 || dig < 1)
		return;
	
	int left = 0;
	int right = size - 1;
	radixSort(arr, left, right, dig);
}

---时间复杂度O(d(n+radix)),空间复杂度O(n)

---按位来排序,这个排序算法的时间复杂度也是比较低的,可以好好研究一下。


打印数组函数:

//打印数组
void PrintArr(int* arr, int size)
{
	assert(arr && size>0);
	for (int i = 0; i < size; ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

我准备了测试用例如下:

int main()
{
//4个测试用例
	//int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int array[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; 
	//int array[10] = { 0, 1, 5, 3, 7, 6, 2, 4, 8, 9 };
	//int array[10] = { 1, 3, 5, 0, 7, 6, 9, 8, 4, 2 };
	

	//int arrayTest[15] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ,23,45,32,77,29};
	
	//BubbleSort(array, 10);

	//InsertSort(array, 10);

	SelectSort(array, 10);

	//HeapSort(array, 10);

	//ShellSort(array, 10);

//快速排序
	//数组形式newArr[]的快排
	//QuickSort1(array, 10);
	//QuickSort2(array, 10);
	//QuickSort3(array, 10);
	//可用于数组和链表的快排
	//QuickSort4(array, 10);
	//优化的快速排序
	// QuickSort5(arrayTest, 15);
	//PrintArr(arrayTest, 15);
	//QuickSort5(array, 10);

	//MergeSort(array, 10);

	//RadixSort(array, 10, 1);
	//CountingSort(array, 10);
	PrintArr(array, 10);



	//int array[12] = {332,633,59,589,232,664,179,457,825,714,405,361}; //基数排序数组
	//RadixSort(array, 12, 3);
	//PrintArr(array, 12);
	return 0;
}

以上就是常见的排序算法,有一些我也没有研究透,所以不敢乱下结论,怕误导大家,还请见谅。

你可能感兴趣的:(排序,算法)