快速排序的使用

快速排序是冒泡排序的优化版,效率更高

假设对以下数据进行快速排序

接下来介绍实现快速排序的方法(用升序来举例)

挖坑法

1.1 单趟排

先将第一个数据设为key,然后经过单趟的排序,使key的左边都小于key,key的右边都大于key

上面动图的实现代码

void PrintArr(int* arr, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}
void QuickSort(int* arr, int n)
{
	int left = 0, right = n - 1;
	int key = arr[left];
	int pivot = left;
	while (left < right)
	{
		//右边找小,放到左边
		while (left < right && arr[right] >= key)
		{
			right--;
		}
		arr[pivot] = arr[right];//填坑
		pivot = right;//right位置的数据被移走,形成新的坑


		//左边找大,放到右边
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[pivot] = arr[left];//填坑
		pivot = left;//left位置的数据被移走,形成新的坑

	}
	//注意!要注意判断是先找小还是先找大
	//此处排升序,所以必须要先在右边找小,然后左边找大
	//因为第一次的坑在左边,所以要右边找小的放在左边

	arr[pivot] = key;

}

void TestQuickSort()
{
	int arr[] = { 6,1,2,7,9,3,4,10,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	QuickSort(arr, sz);
	PrintArr(arr, sz);
}

int main()
{
	TestQuickSort();
	return 0;
}

1.2 完整版排序

经过单趟排之后,我们可以想到分治的思路,用递归,让key的左子区间和右子区间都有序

#include <stdio.h>
void PrintArr(int* arr, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}

void QuickSort(int* arr, int begin,int end)
{
	if (begin >= end)
	{
		return;
	}

	int left = begin, right = end;//一组待排序的数据,left是这组数据的第一个数据,right是最后一个数据
	int key = arr[left];//key取的是每组数据的第一个数
	int pivot = left;
	while (left < right)
	{
		//右边找小,放到左边
		while (left < right && arr[right] >= key)
		{
			right--;
		}
		arr[pivot] = arr[right];//填坑
		pivot = right;//right位置的数据被移走,形成新的坑


		//左边找大,放到右边
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[pivot] = arr[left];//填坑
		pivot = left;//left位置的数据被移走,形成新的坑

	}
	//注意!要注意判断是先找小还是先找大
	//此处排升序,所以必须要先在右边找小,然后左边找大
	//因为第一次的坑在左边,所以要右边找小的放在左边

	arr[pivot] = key;

	QuickSort(arr, begin, pivot - 1);//让左子区间有序
	QuickSort(arr, pivot + 1, end);//让右子区间有序
}

void TestQuickSort()
{
	int arr[] = { 6,1,2,7,9,3,4,10,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	QuickSort(arr, 0, sz - 1);
	PrintArr(arr, sz);
}

int main()
{
	TestQuickSort();
	return 0;
}

1.3 三数取中优化

如果数组的第一个是最大的数或最小的数,就会出现最坏情况,为了避免最坏情况发生,有三数取中的办法进行优化

思路:比较left,right和mid的大小,取中间的数当key,并且将其与第一个交换位置

int GetMidIndex(int* arr, int left, int right)
{
	int mid = (left + right) / 2;
	int arr2[3] = { arr[left],arr[right],arr[mid] };
	QuickSort(arr2, 0, 2);
	if (arr[left] == arr2[1])
	{
		return left;
	}
	else if (arr[right] == arr2[1])
	{
		return right;
	}
	else
	{
		return mid;
	}
	
}
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
void QuickSort(int* arr, int begin,int end)
{
	if (begin >= end)
	{
		return;
	}

	int left = begin, right = end;
	int index = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[index]);
	int key = arr[left];
	int pivot = left;
	while (left < right)
	{
		//右边找小,放到左边
		while (left < right && arr[right] >= key)
		{
			right--;
		}
		arr[pivot] = arr[right];//填坑
		pivot = right;//right位置的数据被移走,形成新的坑


		//左边找大,放到右边
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[pivot] = arr[left];//填坑
		pivot = left;//left位置的数据被移走,形成新的坑

	}
	//注意!要注意判断是先找小还是先找大
	//此处排升序,所以必须要先在右边找小,然后左边找大
	//因为第一次的坑在左边,所以要右边找小的放在左边

	arr[pivot] = key;

	QuickSort(arr, begin, pivot - 1);
	QuickSort(arr, pivot + 1, end);
}

1.4 序列长度达到一定大小时,使用插入排序

当快排达到一定深度后,划分的区间很小时,再使用快排的效率不高。当待排序列的长度达到一定数值后,可以使用插入排序。由《数据结构与算法分析》(Mark Allen Weiness所著)可知,当待排序列长度为5~20之间,此时使用插入排序能避免一些有害的退化情形。

假设当有一百万个数据,用快速排序,可以看到最后几层的排序就调用了很多次QuickSort函数。

例如当深度很深,排序十个数据时,要调用几十万次函数,效率较低。

所以可以使用插入排序

void QuickSort(int* arr, int begin,int end)
{
	if (begin >= end)
	{
		return;
	}

	int left = begin, right = end;
	int index = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[index]);
	int key = arr[left];
	int pivot = left;
	while (left < right)
	{
		//右边找小,放到左边
		while (left < right && arr[right] >= key)
		{
			right--;
		}
		arr[pivot] = arr[right];//填坑
		pivot = right;//right位置的数据被移走,形成新的坑


		//左边找大,放到右边
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[pivot] = arr[left];//填坑
		pivot = left;//left位置的数据被移走,形成新的坑

	}
	//注意!要注意判断是先找小还是先找大
	//此处排升序,所以必须要先在右边找小,然后左边找大
	//因为第一次的坑在左边,所以要右边找小的放在左边

	arr[pivot] = key;

	if (pivot - 1 - begin > 10)
	{
		QuickSort(arr, begin, pivot - 1);

	}
	else
	{
		InsertSort(arr, begin, pivot - 1);
	}


	if (end - pivot - 1 > 10)
	{
		QuickSort(arr, pivot + 1, end);

	}
	else
	{
		InsertSort(arr, pivot + 1, end);

	}

}

文章动图借用自csdn博主“双鱼211”

非递归写法

使用栈来模拟递归调用过程,避免了递归造成的函数栈溢出和效率问题。

程序首先将整个数组的左右端点入栈。接下来,程序进入循环,每次从栈顶弹出一个元素来进行操作。如果右端点小于等于左端点,则说明该子数组不需要进行排序,直接进入下一轮循环;否则,程序选取最右侧的元素作为基准元素,并将数组中小于基准元素的元素放到左侧,大于基准元素的元素放到右侧。然后,程序将排序后的左右两个子数组的左右端点入栈。当栈为空时,整个数组就完成了排序。

typedef int ElemType;
typedef struct Stack
{
	int top;//栈顶
	ElemType* a;//后续使用动态开辟空间
	int capacity;//容量
}ST;
void StackInit(ST* ps)//初始化
{
	assert(ps);
	ps->top = 0;
	ps->a = NULL;
	ps->capacity = 0;
}
void StackPush(ST* ps, ElemType x)//增
{
	assert(ps);
	//检查空间,如果满了就二倍增容
	//如果是第一次增容则先开辟四个空间
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : (2 * ps->capacity);
		ElemType* tmp = (ElemType*)realloc(ps->a, sizeof(ST) * newcapacity);
		//如果ps->a为NULL,则此时realloc的作用与malloc一样
		if (tmp == NULL)
		{
			printf("realloc fail!\n");
			exit(0);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
		
	}
	ps->a[ps->top] = x;
	ps->top++;
}
bool StackEmpty(ST* ps)//判断是否为空
{
	assert(ps);
	if (ps->top == 0)//为空
	{
		return true;
	}

	else//不为空
	{
		return false;
	}
}
void StackPop(ST* ps)//删
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
	printf("栈顶数据删除成功\n");
}
ElemType StackSize(ST* ps)//判断栈的长度
{
	assert(ps);
	return ps->top;
}
ElemType StackTop(ST* ps)//查看栈顶
{
	assert(ps);
	//找栈顶的话得保证指向的空间不为空
	assert(!StackEmpty(ps));
	//此时的top-1就是栈顶数据
	return ps->a[ps->top - 1];
}
void StackDestory(ST* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
	}
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}
int PartSort(int* arr, int begin, int end)
{
	
	int left = begin, right = end;
	int key = arr[left];
	int pivot = left;
	while (left < right)
	{
		//右边找小,放到左边
		while (left < right && arr[right] >= key)
		{
			right--;
		}
		arr[pivot] = arr[right];//填坑
		pivot = right;//right位置的数据被移走,形成新的坑


		//左边找大,放到右边
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[pivot] = arr[left];//填坑
		pivot = left;//left位置的数据被移走,形成新的坑

	}
	
	arr[pivot] = key;

	return pivot;
}
void QuickSortNonS(int* arr, int n)
{
	int left = 0, right = 0;
	ST st;
	StackInit(&st);
	StackPush(&st, n - 1);
	StackPush(&st, 0);

	while (!StackEmpty(&st))
	{
		left = StackTop(&st);
		StackPop(&st);

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

		int IndexMid = PartSort(arr, left, right);

		if (left < IndexMid - 1)//左部分入栈
		{
			
			StackPush(&st, IndexMid-1);
			StackPush(&st, left);
		}

		if (right > IndexMid + 1)//右部分入栈
		{
			StackPush(&st, right);
			StackPush(&st, IndexMid + 1);

		}
	}


	StackDestory(&st);
}
void PrintArr(int* arr, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}

void TestQuickSort()
{
	int arr[] = { 6,1,2,7,9,3,4,10,8,3456,15,4,-348 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	QuickSortNonS(arr, sz);
	PrintArr(arr, sz);
}
int main()
{
	TestQuickSort();
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值