选择排序的三种方法和递归非递归实现

快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

本文先介绍将要排序的数据分割成两个独立两部分的三种方法,我给它们起名为左右交换法、挖坑法、跟随交换法,其次介绍如何递归使用这些方法和非递归使用,最后介绍对快速排序优化的两种方法(借用其他排序和三数取中)

本文所有排序都为升序


左右交换法

思想步骤
思想:随机选取一个值作为比较值,从左选择比这个值大的,从右选择比这个值小的,两两交换,最终达到左小右大的目的。
步骤:

  1. 假设选取最右边的指为比较值。
  2. 从左边找比比较值大的元素,从右边找比比较值小的元素。
  3. 交换左右找到的元素
  4. 循环2和3
  5. 当左右相遇,将相遇指向的元素和比较值交换。

动图演示:
在这里插入图片描述最终在比较值左边的都比比较值小,右边的都比比较值大,这样也就确定了比较值在数组中的位置,并且又得到两个新的分组。
代码实现:

int _QuickSort_1(int* arr, int left, int right)
{
	int key = arr[right];//设置最右边的数为比较值
	int index = right;//保留right保证最后的交换
	while (left<right)
	{
		while (left<right && arr[left]<=key)
		{
			++left;
		}
		while (left<right && arr[right] >= key)
		{
			--right;
		}
		Swap(&arr[left], &arr[right]);
	}
		Swap(&arr[left], &arr[index]);//Left和Right相遇
	return left;//返回分割结点下标
}

挖坑法

思想步骤
思想:随机选取一个值作为比较值并保存在一个变量中,先从左边找到比他大的放到选取的比较值的位置,再从右边找到比他小的放到前一步找到的大的元素的位置,这样往复,最终也能达到左小右大的目的。
步骤:

  1. 假设选取最右边的指为比较值,并且将他存入变量key中。
  2. 从左边找比比较值大的元素,找到后将其赋值到比较值的位置。
  3. 从右边找比比较值小的元素,找到后将其赋值到步骤2找到的位置。
  4. 再从左边找值,赋值到步骤3找到的位置。
  5. 重复步骤3和步骤4,直到左右相遇。
  6. 将key赋值到最后相遇的位置的结点。

动图演示:
在这里插入图片描述代码实现:

int _QuickSort_2(int* arr, int left, int right)
{
	int index=TreeCatchMid(arr, left, right);
	Swap(&arr[index], &arr[right]);
	int key = arr[right];
	while (left < right)
	{
		while (left < right && arr[left] <= key)
		{
			++left;
		}
		arr[right] = arr[left];
		while (left < right && arr[right] >= key)
		{
			--right;
		}
		arr[left] = arr[right];
	}

	arr[right] = key;
	return right;
}

跟随交换法

思想步骤:
思想:随机选取一个数设置为比较值。设置两个指针,cur指针遍历整个数组,prev指针跟在cur后面,每次cur遇到比比较值小的,prev也跟着+1,并交换cur和prev指向结点。当cur遇到比比较值大的,prev就不增加,也不交换。这样,prev总是能指向比比较值大的结点序列头部,然后和cur交换,把大的结点交换到后面,小的交换到前面来。
步骤:

  1. 假设选取最右边的指为比较值,并且将他存入变量key中。
  2. 设置变量cur为左边第一个元素下标,prev跟随cur为cur前一个下标。
  3. 若cur指向结点比key小于等于,prev+1,交换cur指向元素和prev指向元素,cur++。若cur指向结点大于key,cur++。
  4. 重复步骤3直到cur指向尾结点。
  5. 将prev+1,交换key结点元素和prev指向元素。

动图演示:
在这里插入图片描述代码实现:

int _QuickSort_3(int* arr, int left,int right)
{
	int cur = left;
	int prev = cur - 1;
	int key = arr[right];
	while (cur < right)
	{
		while (arr[cur] < key&&arr[++prev] != arr[cur])
		//若prev和cur指向相同便不用交换
		{
			Swap(&arr[cur], &arr[prev]);
		}
		++cur;
	}
	++prev;
	Swap(&arr[prev], &arr[right]);
	return prev;
}

递归实现

通过调用一次上面的函数,就可以把数组分成两部分,且能确定一个位置的元素,并返回确认位置的下标。设div为返回时确认位置的下标,则可以再进行调用,此时被调用的区间为[left,div-1]和[div+1,right]。递归终止条件是,当right小于等于left时,这时数组只有一个元素,不用再进行排序。

void QuickSort_3(int* arr, int left,int right)
{
//递归终止条件,因为div传进来时候可能为0;所以有left>right的可能
	if (left >= right)
		return;

	int div = _QuickSort_3(arr, left, right);

	QuickSort_3(arr, left, div - 1);
	QuickSort_3(arr, div + 1, right);
}

非递归实现

递归是通过函数调用,每次传递新的左右下标进行排序,非递归实现的思想是将每次新的左右下标压入栈中,在进行成对出栈,每次对出栈的左右下标做排序,这样便能排序整个数组。

void QuickSortNR(int* arr, int left, int right)//非递归快速排序,用栈
{
	//1、建栈,存放左右下标
	Stack s;//建栈
	StackInit(&s);
	StackPush(&s, left);
	StackPush(&s, right);
	while (!StackEmpty(&s))
	{
		int end = StackTop(&s);//先出右下标
		StackPop(&s);

		int begin = StackTop(&s);
		StackPop(&s);

		int div = _QuickSort_3(arr, begin, end);

		//2、不断将新的左右下标压栈,每次分出两对
		if (begin < div - 1)
		{
			StackPush(&s, begin);
			StackPush(&s, div - 1);
		}
		if (end > div + 1)
		{
			StackPush(&s, div + 1);
			StackPush(&s, end);
		}
	}
}

由于用的是C语言,没有库,所以我们需要自己写一个栈。文末附有建栈的代码。


选择排序的优化

  • 借用其他排序

前面已经了解了,快速排序采用的是分治的策略,每次将问题划分成两个子问题,就会像数一样,每一层都会进行2^n(n从0开始)函数调用,直到最后剩一个元素为止。
在这里插入图片描述但是如果需要排序的数很多,层数越多也就需要更多的函数函数调用。很可能导致栈溢出,这是我们不妨加个条件,当数组元素小于10的时候进行插入排序,这样便能减少递归调用的层数,减少调用函数的次数。

  • 三数取中

如果像上面的快速排序的写法,就会有一个致命的缺点,如果数组本来就是有序的,那么进行排序会造成时间复杂度为o(n^2)。
所以,对于取比较值,我们从数组中选取三个元素,并从这三个元素中选取中间值左右比较值,这样,就能保证如果数组本来就是有序时的时间复杂度为o(nlogn)。
代码实现:

int TreeCatchMid(int* arr, int left, int right)//三数取中
{
	int mid = left + (right - left) / 2;
	if (arr[left] < arr[right])
	{
		if (arr[mid] < arr[right])
		{
			if (arr[left] < arr[mid])
				return mid;
			else
				return left;
		}
		else
			return right;
	}
	else//left>right
	{
		if (arr[mid] < arr[right])
		{
			return right;
		}
		else if (arr[mid] < arr[left])//mid>right
		{
			return mid;
		}
		else //mid>left>right
			return left;
	}
}

建栈代码

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}Stack;

void StackInit(Stack* ps) //初始化
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}
void StackDestory(Stack* ps)//销毁
{
	assert(ps);
	if (ps->a != NULL)
	{
		free(ps->a);
		ps->top = 0;
		ps->capacity = 0;
		ps->a = NULL;//释放完空间应当保留其初始状态
	}
}
void StackPush(Stack* ps, STDataType x)//插入
{
	assert(ps);
	if (ps->capacity == ps->top)
	{
		int newspace = (ps->capacity == 0 ? 2 : ps->capacity * 2);
		ps->capacity = newspace;
		ps->a = (STDataType*)realloc(ps->a, sizeof(STDataType)*newspace);
		//当realloc中,地址为空则和malloc用法相同
	}
	ps->a[ps->top] = x;
	ps->top++;
}
void StackPop(Stack* ps) //删除
{
	assert(ps && ps->top != 0);
	--ps->top;
}
STDataType StackTop(Stack* ps)//返回栈顶元素
{
	assert(ps&&ps->top != 0);
	return ps->a[ps->top - 1];
}
bool StackEmpty(Stack* ps) //判空
{
	assert(ps);
	return !ps->top;
}
int StackSize(Stack* ps)//求长
{
	assert(ps);
	return ps->top;
}
发布了35 篇原创文章 · 获赞 13 · 访问量 2113

猜你喜欢

转载自blog.csdn.net/weixin_42458272/article/details/97531105