排序 - - - 交换排序(快速排序、冒泡排序)

 

交换排序:

通过排序表中两个元素的比较,若与排序要求相逆(不符合升序或降序),则将两者交换。


快速排序:

基本思想:任取待排序序列中的某个元素作为标准(也称为支点、界点,一般取第一个元素),通过一次划分,将待排元素分为左右两个子序列,左子序列元素的排序码均小于基准元素的排序码,右子序列的排序码则大于或等于基准元素的排序码,即找到该元素在数组中顺序排序的位置。然后分别对两个子序列继续进行划分,直至每一个序列只有一个元素为止。最后得到的序列便是有序序列。

排序 - - - 交换排序(快速排序、冒泡排序)_第1张图片

排序 - - - 交换排序(快速排序、冒泡排序)_第2张图片

int Partition(int* arr, int left, int right)
{
	int key = arr[left];

	while(left < right)
	{
		while(left < right && arr[right] >= key) right--;
		arr[left] = arr[right];

		while(left < right && arr[left] <= key) left++;
		arr[right] = arr[left];
	}
	arr[left] = key;
	return left;
}
 
void Quick(int* arr, int start, int end)
{ 
	if(start < end)
	{
		int index = Partition(arr, start, end);
		Quick(arr, start, index - 1);
		Quick(arr, index + 1, end);
	}
}

void QuickSort(int* arr, int len)
{
	 Quick(arr, 0, len - 1);
}

非递归调用实现快速排序

 排序 - - - 交换排序(快速排序、冒泡排序)_第3张图片排序 - - - 交换排序(快速排序、冒泡排序)_第4张图片

//非递归快速排序
void QuickSort(int* arr, int len)
{
	std::stack s;
	int  left = 0;
	int right = len - 1;
	int index = Partition(arr, left, right);

//第一次划分,得到两个待排序列,分别记录(入栈)
	//小于基准元素左序列
	if(index - 1 > left)   
		{
			s.push(left);   //左序列的开始
			s.push(index - 1);    //左序列的结束
		}

		//小于基准元素右序列
		if(index + 1 < right)
		{
			s.push(index + 1);//右序列的开始
			s.push(right);//右序列的结束
		}

//得到新的序列(出栈),对该序列再进行划分,栈为空则无法划分(排序完成)
	while(!s.empty())
	{
		
		right = s.top();
		s.pop();
		left = s.top();
		s.pop();
		index = Partition(arr, left, right);

		if(index - 1 > left)
		{
			s.push(left);
			s.push(index - 1);
		}
		if(index + 1 < right)
		{
			s.push(index + 1);
			s.push(right);
		}
	}
}

效率分析:如果每次划分对一个对象定位后,该对象的左子序列与右子序列的长度相同, 则下 一步将是对两个长度减半的子序列进行排序, 这是最理想的情况。快速排序的最好时间复杂度为O(nlog2n)。在最坏的情况, 即待排序对象序列已经按其排序码从小到大排好序的情况下,快速排序的最坏时间复杂度为O(n2)

快速排序最好的空间复杂度为O(log2n),最坏的空间复杂度为O(n)(即快速排序所需用的辅助空间)

 快速排序是一种不稳定的排序方法。

 

快速排序的优化:

基于快速排序的思想,每一次划分,两个新序列长度越接近,排序的总效率越高(数组中的数越随机越好)。

1、三数取中法(将开始、中间、结尾坐标互换),对基本有序的数组进行优化。

//三数取中,使left下标元素大小处于中间
void GetMidNum(int*arr, int left, int mid, int right)
{
	if(arr[left] > arr[right])
		Swap(arr, left, right);

	if(arr[mid] > arr[left])
		Swap(arr, mid, left);

	if(arr[mid] > arr[right])
		Swap(arr, mid, right);
}

2、随机选取表准基数,对于基本有序数组进行优化。

	 //随机选取基准
	srand((unsigned int)time(NULL));
	Swap(arr, start, rand()%(end - start +1) + start);
		 

3、对于小序列采用插入排序,当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差。

//对于小序列采用插入排序
if(end - start < 10)
{
    InsertSort(arr, end - start +1);
    return ;
}
else
{
//快速排序
}

4、处理重复元素:划分过程中将与表准基数相同的数放在两端并记录;换分完成后,再将相同元素交换到表准基数两旁。

int Partition(int* arr, int low, int high, int& repeat)
{
	int left = low;
	int right = high;
	int key = arr[left];
	int leftlen = 0;
	int rightlen = 0;

	while(left < right)
	{
		while(left < right && arr[right] >= key) 
		{
			//处理相等元素 
			if(arr[right] == key)
			{
				Swap(arr, right, high-rightlen);//把右边与key元素相等的聚集的右端
				++rightlen;
			}
			right--;
		}
		arr[left] = arr[right];

		while(left < right && arr[left] <= key)
		{
			//处理相等元素 
			if(arr[left] == key)
			{
				Swap(arr, left, low+left);//把左边与key元素相等的聚集数组的左端
				++leftlen;
			}
			left++;
		}
		arr[right] = arr[left];
	}
	arr[left] = key;
	repeat = 0; //记录重复出现的次数
	repeat = leftlen + rightlen;

	//相同元素交换到表准基数两旁
	for(int i = 0; i < leftlen; i++)
	{
		Swap(arr, low + i, left - i -1);
	}
	for(int i = 0; i < rightlen; i++)
	{
		Swap(arr, high - i, left +i +1);
	}
	
	return left - leftlen; //返回重复数字最左端下标
}

 



冒泡排序:

通过对待排序列从前向后,依次比较相邻元素的大小,如果逆序则交换,使较大的元素逐渐后移。

排序 - - - 交换排序(快速排序、冒泡排序)_第5张图片

效率分析:从冒泡排序的算法可以看出,若待排序的元素为正序,则只需进行一趟排序,比较次数为(n-1)次,移动元素次数为0;若待排序的元素为逆序,则需进行n-1趟排序,比较次数为(n2-n)/2,移动次数为3(n2-n )/2,因此冒泡排序算法的时间复杂度为O(n2)。顺序情况下为O(n),由于其中的元素移动较多,所以属于内排序中速度较慢的一种。

因为冒泡排序算法只进行元素间的顺序移动,所以是一个稳定的算法。 

void BubbleSort(int* arr, int len)
{
	int tmp = 0;
	bool flag = false;
	for(int i = 0; i < len-1; ++i)
	{
		for(int j = 0; j < len -1 -i; ++j)
		{
			flag = false;
			//将较大元素后移
			if(arr[j+1] < arr[j])
			{
				flag = true;
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
		//没有交换,结束
		if(!flag)
		{
			break;
		}
	}
}

你可能感兴趣的:(数据结构,快速排序,冒泡排序,非递归快排,快排优化)