408排序盘点----内部排序小结:冒泡、插入、希尔、选择、堆、快速排序基于C++vector容器全实现;冒泡、插入、选择排序的区别;插入&&希尔的联系;选择&&堆排序的联系;大根堆小根堆的实现细节。

20 篇文章 1 订阅
3 篇文章 1 订阅

408考纲内的内部排序主要分成以下几个部分

目录

一、冒泡排序

二、插入排序-->希尔排序

三、选择排序-->堆排序

选择排序算法思想:

堆排序算法思想:

四、冒泡、插入、选择排序的区别

五、快速排序

六、408考纲内的排序各算法复杂度分析与稳定性分析与小结

七、所有内部排序算法代码汇总与测试结果


学习者看这篇文章代码和文字描述应当结合看,顺序和逆序的看代码和文字描述都是不可取的,另外就应试来说,快速排序那里该加上等于号的地方要加上等于号,快速排序在应试中就写不稳定。

大佬看这篇文章请直接移步快速排序,我的代码逻辑使得快速排序对于{2,2,1}数据样本稳定,举多数数据样本依然可以排序,不知道有没有大佬知道我这个快速排序写的有没有问题,有问题欢迎指出,这是困扰我的一点,另外就应试来说,那个该加上等于号的地方要加上等于号,快速排序在应试中就写不稳定。

一、冒泡排序

算法思想:

无需调整

外循环i控制后子序列的头部位置,初始不设置前有序子序列,初始把整个数组看作子序列,

内循环j对于不同轮的i(每个后子序列)都从最后一个元素开始,相邻元素比较一直比到j==i+1结束也就是说最后一轮循环是i和i+1位置元素比较也就是j>i时才进入内循环,否则不进入。

稳定

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

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

void bubble_sort(vector<int>& a)
{
	vector<int>::iterator i = a.begin();
	for (; i < a.end() - 1; i++)
	{
		bool flag = false;
		vector<int>::iterator j = a.end() - 1;
		for (; j > i; j--)
		{
			if (*(j - 1) > *j)
			{
				swap(*(j - 1),*j);
				flag = true;//发生了元素交换事件
			}
		}
		if (flag == false)return;
	}
}
void print(int a)
{
	cout << a<<" ";
}
int main()
{
    //冒泡排序
	//vector<int> a = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> a = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(a.begin(), a.end(), print);
	cout << endl;
	bubble_sort(a);
	cout << "冒泡排序的结果为:";
	for_each(a.begin(), a.end(), print);
	cout << endl;


    system("pause");
    return 0;
}

二、插入排序-->希尔排序

插入排序是希尔排序d设置为1时候的希尔排序,希尔排序的每个子序列内的排序都是直接插入排序,希尔排序的内循环部分和直接插入排序的思想一模一样。

插入排序算法思想:

①调整

顺序表前空出init_d=size()/2的位置,以便解决C++vector迭代器在排序中的越界问题,输出时从begin()+init_d位置开始输出。第一个有效位置从begin()+init_d位置开始

初始设置前有序子序列,初始认为第一个有效位置为前有序子序列

将newbegin=begin()+init_d-1位置设为哨兵,哨兵每次选后子序列的第一个位置,也就是这个位置的元素的值先赋给newbegin()位置,哨兵拿着手上的元素对比前有序子序列中的每个元素一边比较一边右移,直到比到第一各比哨兵手上的元素小的j位置所指元素也就是*j<*newbegin时,而后应该把哨兵手上的元素插到j位置的后面也就是j+1位,而不是j位置,这是需要注意的一个细节。

插入和希尔的区别:

插入排序和希尔排序的不同点在于插入排序稳定,希尔不稳定,插入排序时间复杂度为n²,希尔排序无法确定时间复杂度

希尔排序过程中的注意点:

假定希尔本轮划分4个希尔划分序列进行插入排序,那么在希尔的整个过程中i循环控制变量紧挨着的两轮是对不同的子序列进行排序,和时间片轮转调度的思想一致,再具体一点比如说本轮我在第一个子序列中选择后子序列的第一个位置的元素进行插入,找到这个元素该插入的位置之后我不是继续在该希尔划分的子序列的后子序列进行排序而是在下一个希尔划分中执行插入排序。直到上一轮所有希尔划分的后子序列的第一个位置元素都确定了一轮中的位置之后,才会从第一个希尔划分开始,确定上一轮第一个希尔划分后子序列第二个位置的元素在该子序列中的位置。这个理解建议结合代码的过程来看:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

void print(int a)
{
	cout << a << " ";
}

void Insertsort(vector<int>& a)
{
	//先挪出哨兵位置
	a.push_back(0);
	for (vector<int>::iterator it = a.end() - 1; it > a.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(* a.begin()) = 0;

	//对比希尔排序:i =newbegin+1+d
	vector<int>::iterator i = a.begin() + 2;//第一步从第一个位置的下一个位置入手,认为这个位置之前的元素是排好序的
	//在前面排好序的子序列中找处所有比当前i位置所指元素更大的元素全部右移而后将i位置元素插入前面的有序序列的空位
	//防止迭代器异常选取带哨兵的算法可能好一些
	//将需要插入的元素交给哨兵,一边让哨兵将各个元素和他手上的元素对比一边将比哨兵手上大的元素右移
	for (; i < a.end(); i++)
	{

		if (*i < *(i - 1))//可以考场上临时进行优先级判定也可区分
		{
			*a.begin() = *i;
			vector<int>::iterator j = i - 1;
			for (; (*j) > *a.begin(); j--) *(j+1) = *j ;//j位置比哨兵大则将j元素右移到j+1位置//一边对比,一边右移
			*(j + 1) = *a.begin();//注意,j+1位置才是我们应该插入的位置,应为必须要确定插入位置的前驱一定比哨兵手上的小
									//所以算法内部会将j指针向前挪一个再和前一个元素比较一下再确定这个元素的摆放位置应该摆在这个元素之后即此时的j+1位置
		}

	}

}

void shellsort(vector<int>&a,int init_d)
{
	int d = (a.size()-init_d)/2;
	vector<int>::iterator i = a.begin() + init_d;
	vector<int>::iterator j;
	vector<int>::iterator newbegin = a.begin() + init_d-1;//哨兵位置
	for (; d >= 1; d /= 2)
	{
		//以下和插入排序的思想一摸一样,插入排序是d恒等于1的希尔排序
		//插入排序和希尔排序的不同点在于插入排序稳定希尔不稳定,插入排序时间复杂度为n平方,希尔排序无法确定时间复杂度
		for (i =newbegin+1+d; i < a.end(); i++)//第一次对比第一个子表,第二次对比第二个子表.轮流进行
		{	
			if (*i < *(i - d))
			{
				*newbegin = *i;//将i所指的元素保存在哨兵位置
				for (j = i - d; j > newbegin &&  *j >* newbegin; j -= d)
					*(j + d) = *j;//一边右移一边对比
				*(j + d) = *newbegin;
			}
		}
	}
}

int main()
{
    //直接插入排序
	//vector<int> b = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> b = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(b.begin(), b.end(), print);
	cout << endl;

	Insertsort(b);
	cout << "直接插入排序的结果为:";
	for_each(b.begin()+1, b.end(),print);
	cout << endl;
	



	//希尔排序
	//vector<int> c = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> c = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(c.begin(), c.end(), print);
	cout << endl;

	//调整,使得算法vector的迭代器,空出begin()之前init_d位置补0,只在begin()+init_d位置到end()-1位置的数据排序,输出时只输出这一部分内容
	int init_d = c.size() / 2;
	for (int i = 0; i < init_d; i++)
		c.push_back(0);
	for (vector<int>::iterator it = c.end() - 1; it>=c.begin()+init_d; it--)
		(*it) = *(it - init_d);
	for (int i = 0; i < init_d; i++)
		*(c.begin() + i) = 0;
	//for_each(a.begin(), a.end(), print);//测试代码,验证已经在序列前插入辅助位置,避免迭代器异常
	shellsort(c,init_d);
	cout << "希尔排序的结果为:";
	for_each(c.begin() + init_d, c.end(), print);
	cout << endl;


    system("pause");
    return 0;
}

三、选择排序-->堆排序

选择排序,又称为“简单选择排序”是所有排序中过程和代码实现最简单直观好理解的一种排序

选择排序算法思想:

无需调整

初始不设置前有序子序列,从整个数组开始,初始把整个数组看作后子序列。

每次遍历后子序列,选择min放到该轮后子序列的首部,而后下一轮定义后子序列从这个元素的下一个位置开始一直到整个数组的最后一个元素结束,重复过程直到下一轮后子序列的第一个位置为整个数组的最后一个位置结束也就是下一轮i指向end()-1时结束。

关于堆数据结构和操作系统的堆栈的堆、程序运行时的堆栈段的堆的区别,以下是文心一言AI给出的回答:

在 C++ 中,堆栈段(stack segment)是指用于存储函数调用信息的一段内存空间。在 C++ 中,堆栈段是一个特殊的数据段,其存储的信息与操作系统内存管理中的堆栈(stack)并不完全相同。

在操作系统内存管理中,堆栈是一个用于存储函数调用信息的数据结构。堆栈由一系列栈帧(stack frame)组成,每个栈帧都包含了函数调用的信息,例如函数名、地址、参数等。当一个函数被调用时,操作系统会将函数的返回地址和其他信息压入堆栈中,以便在函数返回时能够正确地返回。

在 C++ 中,堆栈段是一个用于存储函数调用信息的数据结构,其存储的信息与操作系统内存管理中的堆栈类似。每个堆栈段都可能有不同大小的内存空间,大根堆栈(Large-Heap Stack)和小根堆栈(Small-Heap Stack)是其中的两种。

大根堆栈是指在堆栈段中存储的数据大小为堆栈帧大小(stack frame size)的整数倍,例如 4 个栈帧的大小。而小根堆栈则是指在堆栈段中存储的数据大小为堆栈帧大小的整数倍的相反数,例如 4 个栈帧的大小。

在 C++ 中,堆栈段的大小通常是固定的,并且被视为程序的一部分,不能动态调整。然而,可以通过对堆栈段进行分配和释放来调整堆栈的大小。例如,可以使用 new 和 delete 运算符来分配和释放堆栈段的内存空间。

总之,在 C++ 中,堆栈段是一个用于存储函数调用信息的数据结构,其存储的信息与操作系统内存管理中的堆栈类似,但是在实现上有所不同。在 C++ 中,堆栈段的大小是固定的,而堆栈可以动态调整。堆栈段和堆的实现可以根据具体情况进行选择,取决于程序的需求和性能要求。

在操作系统中,堆(heap)是指用于存储程序运行时数据的一段内存空间。堆是操作系统中的一个重要概念,用于管理程序运行时的数据和指针。

在程序运行时,堆的逻辑结构与操作系统的堆栈(stack)并不完全相同。操作系统的堆栈是用于存储函数调用信息的,而程序运行时的堆则是用于存储程序运行时数据和指针的。

在程序运行时,堆的逻辑结构通常比较小,通常只包含几个基本数据类型(如 int、long、float 等)以及指向基本数据类型的指针。这些数据和指针被称为“根”(root),它们是程序运行时数据的基础。

在堆中,还可能包含一些指向堆中根数据的指针,这些指针被称为“子根”(child root)。子根指向的数据可能是其他数据结构,例如寄存器或者内存池中的数据。这种“多级根”的结构使得堆可以存储更多的数据和指针信息,提高了堆的存储容量和数据处理能力。

总之,虽然操作系统的堆和程序运行时的堆在一些方面有相似之处,但它们的实现方式和使用场景有所不同。操作系统的堆栈主要用于存储函数调用信息,而程序运行时的堆则用于存储程序运行时数据和指针。在程序运行时,堆的大小通常比较小,并且可以动态调整。

逻辑结构--堆,分为大根堆小根堆:

堆排序算法思想:

以先建立大根堆而后基于大根堆对前子序列进行选择排序为例,

调整,使得下标和树有逻辑上的对应关系,在数组前设置一个位置为哨兵位置,遍历输出从begin()+1位置开始输出。

step①:大根堆建立Bigheap_build

i从有效数据总数n/2位置开始(第一个非叶结点,这么设计可以通过完全二叉树的性质i,2i,2i+1把所有结点都对比过),哨兵先拿到i位置的元素,哨兵拿着i和i的大孩子作对比,哨兵发现手上的元素比大孩子小则大孩子应该放在i位置,哨兵于此同时从大孩子的位置开始,与大孩子的大孩子比较也就是i的大孙子比,直到哨兵手上的元素比大孩子大则停止,那么哨兵手上的元素就应该放在整个大孩子的父节点位置,需要注意的是,此时假设该父节点为k,大孩子为2k或2k+1,也就是说,哨兵在k位置只有和2k或2k+1位置的元素对比过后才能将手上元素放到k位置,这个注意点和插入排序一致,区别就在于大根堆的建立过程是比较哨兵与2k或2k+1而插入是比较哨兵与j位置,最后元素放的位置堆是放在k位置,也就是2k或2k+1的左边,插入是放在j+1位置也就是j的右边(这个左边和右边不是记忆的重点,因为都是为了构造递增序列的需求,核心在于“位置的确定,我只有确定了逻辑上相邻的两个元素的大小关系我才能确定这个元素应该放在哪):

 以上步骤结束后会出现根<大孩子的情况:

最后再次进入adjust从顶子树不断向下调整即可。

step②:

初始不设置前有序子序列,整个数组有效数据部分看作前有序子序列。定义high指针指向前待排序子序列的最后一个元素,每轮结束后high定界符依次向前移动。

由于已经建立大根堆,所以每轮选择前子序列第一个位置和前子序列最后一个位置元素进行swap交换,相对于选择排序需要遍历待排序序列每轮省略了n-1次、n-2次...次比较,选择大根放到下轮后子序列的首部,而后high--,重新定义前子序列的范围,还要再将前子序列重建大根堆,而后重复上述操作。

下面给出简单选择排序算法、大/小根堆的建立,以及基于大/小根堆构造递增/递减序列的代码实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

void print(int a)
{
	cout << a << " ";
}

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

void selectsort(vector<int>& a)
{
	vector<int>::iterator i = a.begin();
	for (; i < a.end(); i++)
	{
		vector<int>::iterator j = i + 1;
		vector<int>::iterator  min =i;
		for (; j < a.end(); j++)
			if (*j < *min)min = j;
		if (min != i)swap(*min,*i);//i为后子列第一个元素,min为子列中的最小元素,子列内交换

	}

}

void Bigheap_adjust(vector<int>&a,vector<int>::iterator k,vector<int>::iterator high)
{
	
	*a.begin() = *k;
	int bias = k - a.begin();
	vector<int>::iterator i =a.begin()+2*bias;//a.begin()相当于数组下标0
	while (i <=high)
	{
		//k和bias同时修改方可用迭代器实现

		//第一个判断的目的是找左右孩子中最大的孩子
		if (i != high && *i < *(i + 1))
		{	//若i==a,end()则一定没有右子树,故只有i!=a.end()-1才有可能有右子树,才有可能进行i++;
			//如果*i >= *(i + 1)即左孩子大于右孩子那么就不必再向右子树找了,用左孩子和哨兵比较即可
			i++;//左孩子小于根,考察右孩子或右子树
		}
		if (*a.begin() >= *i)break;//i所指为k结点最大的孩子,左右孩子都小于根k(k==i/2),则该元素该放在k位置
		else//k的最大的一个孩子比根大
		{
			//这里不能简单的认为swap(a,b)
			*k = *i;//大的一个孩子i应该放在k位置
			k = i;//在原来k位置的大孩子为顶的子树中继续考察,i指向该子树顶也就是k的大孩子
		}
		bias=i - a.begin();//i指针代数化
		if (2 * bias>a.size()-1)break;
		i = a.begin() + 2 * bias;//形式化
	}
	*k = *a.begin();
}

void Bigheap_build(vector<int>& a,vector<int>::iterator high)
{
	//a.begin()相当于数组下标0
	//大根堆建立
	vector<int>::iterator i = a.begin()+(high-a.begin()) / 2;
	for (; i > a.begin(); i--)
		Bigheap_adjust(a, i,high);

}

void Bigheap_sort(vector<int>& a)
{
	//每一趟将堆顶与待排序序列最后一个元素swap
	for (vector<int>::iterator high = a.end() - 1; high >=a.begin()+2; high--)
	{
		swap(*(a.begin() + 1), *high);
		Bigheap_build(a, high - 1);
	}

}

void smallheap_adjust(vector<int>& a, vector<int>::iterator k, vector<int>::iterator high)
{
	*a.begin() = *k;
	int bias = k - a.begin();
	vector<int>::iterator i = a.begin() + 2 * bias;//a.begin()相当于数组下标0
	while (i <= high )
	{
		//k和bias同时修改方可用迭代器实现

		//第一个判断的目的是找左右孩子中最小的孩子
		if (i != high && *i > *(i + 1))
		{	//若i==a,end()则一定没有右子树,故只有i!=a.end()-1才有可能有右子树,才有可能进行i++;
			//如果*i >= *(i + 1)即左孩子大于右孩子那么就不必再向右子树找了,用左孩子和哨兵比较即可
			i++;//左孩子小于根,考察右孩子或右子树
		}
		if (*a.begin() <= *i)break;//i所指为k结点最小的孩子,左右孩子都大于根k(k==i/2),则该元素该放在k位置
		else//k的最小的一个孩子比根小
		{
			//这里不能简单的认为swap(a,b)
			*k = *i;//小的一个孩子i应该放在k位置
			k = i;//在原来k的小孩子为顶的子树中继续考察,i指向该子树顶也就是k的小孩子
		}
		bias = i - a.begin();//i指针代数化
		if (2 * bias > a.size() - 1)break;
		i = a.begin() + 2 * bias;//形式化
	}
	*k = *a.begin();

}

void smallheap_build(vector<int>& a,vector<int>::iterator high)
{
	//a.begin()相当于数组下标0
	//小根堆建立
	vector<int>::iterator i = a.begin() + a.size() / 2;
	for (; i > a.begin(); i--)
		smallheap_adjust(a, i, high);

	
}

void smallheap_sort(vector<int>& a)
{
	//每一趟将堆顶与待排序序列最后一个元素swap
	for (vector<int>::iterator high = a.end() - 1; high >= a.begin() + 2; high--)
	{
		swap(*(a.begin() + 1), *high);
		smallheap_build(a, high - 1);
	}

}


int main()
{
    //简单选择排序
	vector<int> e = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(e.begin(), e.end(), print);
	cout << endl;
	selectsort(e);
	cout << "简单选择排序的结果为:";
	for_each(e.begin(), e.end(), print);
	cout << endl;



	//堆排序
	//由于2k这种数量关系难以显式的用迭代器表现,所以我们在堆排序的实现中直接用数组进行排序
	//但其实也可以作bias的暂存,再对bias*2+a.begin()也能做..只是不直观
	vector<int> f = {53,17,78,9,45,65,87,32 };
	cout << "待排序的数列为:";
	for_each(f.begin(), f.end(), print);
	cout << endl;
	//建立大根堆
	//首先应该调整,使得i从1开始
	f.push_back(0);
	for (vector<int>::iterator it = f.end() - 1; it > f.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(*f.begin()) = 0;//哨兵位置
	//for_each(f.begin(), f.end(), print);//测试代码

	Bigheap_build(f,f.end()-1);
	cout << "建立的大根堆为:";
	for_each(f.begin()+1, f.end(), print);
	cout << endl;

	//基于大根堆建立递增序列
	Bigheap_sort(f);
	cout << "基于大根堆排序的结果为:";
	for_each(f.begin()+1, f.end(), print);
	cout << endl;
 

	建立小根堆
	vector<int> g = { 53,17,78,9,45,65,87,32 };
	cout << "待排序的数列为:";
	for_each(g.begin(), g.end(), print);
	cout << endl;
	//首先应该调整,使得i从1开始
	g.push_back(0);
	for (vector<int>::iterator it = g.end() - 1; it > g.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(*g.begin()) = 0;//哨兵位置
	//for_each(g.begin(), g.end(), print);//测试代码


	smallheap_build(g,g.end()-1);
	cout << "建立的小根堆为:";
	for_each(g.begin()+1, g.end(), print);
	cout << endl;

	基于小根堆构造递减序列
	smallheap_sort(g);
	cout << "基于小根堆排序的结果为:";
	for_each(g.begin()+1, g.end(), print);
	cout << endl;

    system("pause");
    return 0;
}

四、冒泡、插入、选择排序的区别

//冒泡排序的内层循环是相邻两个比较与交换

        //初始不设置前有序子序列,整张表看作后子序列

        //内层循环j从后向前一直到j在i的后一个位置并走完这轮循环后j和i相等时循环结束

        //也即j>i时则一直循环

//插入排序初始设置第一个位置元素为前有序子序列,选后子序列的第一个位置的元素前面有序子列中的元素逐个的一边对比一边右移找到位置后插入

        //目标位置的确定必须先确定再前一个的元素比哨兵小才能确定目标位置,

        //此时j指向目标的前一个位置,那么元素应该放在j+1位置

//选择排序初始不设置前有序子序列,初始整张表看作后子序列,在后子序列中选min后子序列的第一个位置交换

五、快速排序

给出一种递归实现的算法描述:

也是设置定界符low,high的思想,调整,设置哨兵位,这里给它个特殊的名字叫枢轴元素pivot,调整使得下标和树有逻辑上的对应关系,逻辑上也是把整个序列有效部分看作一颗树,只不过是一个BST树--二叉排序树,设置定界符low和high,初始指向有效数据部分的第一个和最后一个元素,第一级选取low位置作为pivot,low、high分别指向有效数据部分的头和尾,进入partition函数

while (low < high)
    {
        while (low < high && (*high)>=pivot)//注意后一个逻辑判断的等号,注意考察等号,这个等号在这个代码逻辑下必须放在这
            --high;//high所指的元素比枢轴大,符合我们的预设,放在右子树不动
        (*low) = (*high);//high所指的元素比枢轴小,不符合我们的预设,应当移到左子树也就是low所指位置
                        //可以放心覆盖,因为先将low所指元素保存了一份作为pivot了,之后想要原来这个位置的元素可以通过pivot获得,low完全可以被覆盖
        while (low < high && (*low) <pivot)//这里如果后一个判断条件加等号就会造成{2,2,1}的不稳定,但是不加等号却稳定..这里如果有大佬能举例说明存在部分数据无法排序的问题,那么还应当按照课本上的写法写成带等号的。
            ++low;
        
        (*high) = (*low);//一定经过了(*low) = (*high);,high位置已经放到了该放的位置上,不用担心high所指元素被无情覆盖的问题。
                        //也无需担心数据重复的问题,这个语句和(*low) = (*high);语句一起可以抵消掉,覆盖掉重复元素
    }

直到low>=high, (*low) = pivot;//最开始选取的枢轴元素应该放在low所指位置

到此,最大的一颗树的树根位置已确定,整个快速排序可以看作先根遍历,

退出条件为!(low<high)

而后递归确定左子树、右子树的树根

给出代码实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

void print(int a)
{
	cout << a << " ";
}
vector<int>::iterator Partition(vector<int> &a,vector<int>::iterator low,vector<int>::iterator high)
{
	int pivot = (*low);
	while (low < high)
	{
		while (low < high && (*high)>=pivot)//注意后一个逻辑判断的等号,注意考察等号,这个等号在这个代码逻辑下必须放在这
			--high;//high所指的元素比枢轴大,符合我们的预设,放在右子树不动
		(*low) = (*high);//high所指的元素比枢轴小,不符合我们的预设,应当移到左子树也就是low所指位置
						//可以放心覆盖,因为先将low所指元素保存了一份作为pivot了,之后想要原来这个位置的元素可以通过pivot获得,low完全可以被覆盖
		while (low < high && (*low) <pivot)//这里如果后一个判断条件加等号就会造成{2,2,1}的不稳定,但是不加等号却稳定..
											//这里如果有大佬能举例说明存在部分数据无法排序的问题,那么还应当按照课本上的写法写成带等号的。
			++low;
		
		(*high) = (*low);//一定经过了(*low) = (*high);,high位置已经放到了该放的位置上,不用担心high所指元素被无情覆盖的问题。
						//也无需担心数据重复的问题,这个语句和(*low) = (*high);语句一起可以抵消掉,覆盖掉重复元素
	}
	(*low) = pivot;//最开始选取的枢轴元素应该放在low所指位置
	return low;
}

void quicksort(vector<int>& a,vector<int>::iterator low,vector<int>::iterator high)
{
	
	if (low < high)
	{
		vector<int>::iterator pivotpos = Partition(a, low, high);
		quicksort(a, low, pivotpos - 1);//这里可能会遇到pivot==low==high==第一个位置的元素,如果再减1会发生vector迭代器溢出
		quicksort(a, pivotpos + 1, high);//所以需要对原来的数组先进行调整!
	}
}

int main()
{

    //快速排序
	//vector<int> d = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> d = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(d.begin(), d.end(), print);
	cout << endl;
	vector<int>::iterator low = d.begin();
	vector<int>::iterator high = d.end() - 1;
	//调整,使得算法vector的迭代器,空出begin()位置补0,只在begin()+1位置到end()-1位置的数据排序,输出时只输出这一部分内容
	d.push_back(0);
	for (vector<int>::iterator it = d.end() - 1; it > d.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(*d.begin()) = 0;
	low = d.begin() + 1;
	high = d.end() - 1;
	//for_each(d.begin() , d.end(), print);//测试代码
	quicksort(d, low, high);
	cout << "快速排序的结果为:";
	for_each(d.begin() + 1, d.end(), print);
	cout << endl;

    system("pause");
    return 0;
}

基于以上代码的稳定性分析: 

 

基于这个代码发现快速排序居然稳定?这是我在这里遗留的问题,第一个问题是我写的这个能不能叫快速排序或者这个算法有没有什么数据样本是会出现bug的,第二个问题是,如果我写的这个可以叫快速排序那么如何衡量这个算法的稳定性? 排序算法的稳定性是怎么定义的?

已解决:特定排序算法的实现唯一性与稳定性唯一性

 

六、408考纲内的排序各算法复杂度分析与稳定性分析与小结

 在这里插入图片描述

 

 

//希尔排序不讨论它的时间复杂度

七、所有内部排序算法代码汇总与测试结果

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

void print(int a)
{
	cout << a << " ";
}

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

void bubble_sort(vector<int>& a)
{
	vector<int>::iterator i = a.begin();
	for (; i < a.end() - 1; i++)
	{
		bool flag = false;
		vector<int>::iterator j = a.end() - 1;
		for (; j > i; j--)
		{
			if (*(j - 1) > *j)
			{
				swap(*(j - 1),*j);
				flag = true;//发生了元素交换事件
			}
		}
		if (flag == false)return;
	}
}


vector<int>::iterator Partition(vector<int> &a,vector<int>::iterator low,vector<int>::iterator high)
{
	int pivot = (*low);
	while (low < high)
	{
		while (low < high && (*high)>=pivot)//注意后一个逻辑判断的等号,注意考察等号,这个等号在这个代码逻辑下必须放在这
			--high;//high所指的元素比枢轴大,符合我们的预设,放在右子树不动
		(*low) = (*high);//high所指的元素比枢轴小,不符合我们的预设,应当移到左子树也就是low所指位置
						//可以放心覆盖,因为先将low所指元素保存了一份作为pivot了,之后想要原来这个位置的元素可以通过pivot获得,low完全可以被覆盖
		while (low < high && (*low) <pivot)//这里如果后一个判断条件加等号就会造成{2,2,1}的不稳定,但是不加等号却稳定..
											//这里如果有大佬能举例说明存在部分数据无法排序的问题,那么还应当按照课本上的写法写成带等号的。
			++low;
		
		(*high) = (*low);//一定经过了(*low) = (*high);,high位置已经放到了该放的位置上,不用担心high所指元素被无情覆盖的问题。
						//也无需担心数据重复的问题,这个语句和(*low) = (*high);语句一起可以抵消掉,覆盖掉重复元素
	}
	(*low) = pivot;//最开始选取的枢轴元素应该放在low所指位置
	return low;
}

void quicksort(vector<int>& a,vector<int>::iterator low,vector<int>::iterator high)
{
	
	if (low < high)
	{
		vector<int>::iterator pivotpos = Partition(a, low, high);
		quicksort(a, low, pivotpos - 1);//这里可能会遇到pivot==low==high==第一个位置的元素,如果再减1会发生vector迭代器溢出
		quicksort(a, pivotpos + 1, high);//所以需要对原来的数组先进行调整!
	}
}



void Insertsort(vector<int>& a)
{
	//先挪出哨兵位置
	a.push_back(0);
	for (vector<int>::iterator it = a.end() - 1; it > a.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(* a.begin()) = 0;

	//对比希尔排序:i =newbegin+1+d
	vector<int>::iterator i = a.begin() + 2;//第一步从第一个位置的下一个位置入手,认为这个位置之前的元素是排好序的
	//在前面排好序的子序列中找处所有比当前i位置所指元素更大的元素全部右移而后将i位置元素插入前面的有序序列的空位
	//防止迭代器异常选取带哨兵的算法可能好一些
	//将需要插入的元素交给哨兵,一边让哨兵将各个元素和他手上的元素对比一边将比哨兵手上大的元素右移
	for (; i < a.end(); i++)
	{

		if (*i < *(i - 1))//可以考场上临时进行优先级判定也可区分
		{
			*a.begin() = *i;
			vector<int>::iterator j = i - 1;
			for (; (*j) > *a.begin(); j--) *(j+1) = *j ;//j位置比哨兵大则将j元素右移到j+1位置//一边对比,一边右移
			*(j + 1) = *a.begin();//注意,j+1位置才是我们应该插入的位置,应为必须要确定插入位置的前驱一定比哨兵手上的小
									//所以算法内部会将j指针向前挪一个再和前一个元素比较一下再确定这个元素的摆放位置应该摆在这个元素之后即此时的j+1位置
		}

	}

}

void shellsort(vector<int>&a,int init_d)
{
	int d = (a.size()-init_d)/2;
	vector<int>::iterator i = a.begin() + init_d;
	vector<int>::iterator j;
	vector<int>::iterator newbegin = a.begin() + init_d-1;//哨兵位置
	for (; d >= 1; d /= 2)
	{
		//以下和插入排序的思想一摸一样,插入排序是d恒等于1的希尔排序
		//插入排序和希尔排序的不同点在于插入排序稳定希尔不稳定,插入排序时间复杂度为n平方,希尔排序无法确定时间复杂度
		for (i =newbegin+1+d; i < a.end(); i++)//第一次对比第一个子表,第二次对比第二个子表.轮流进行
		{	
			if (*i < *(i - d))
			{
				*newbegin = *i;//将i所指的元素保存在哨兵位置
				for (j = i - d; j > newbegin &&  *j >* newbegin; j -= d)
					*(j + d) = *j;//一边右移一边对比
				*(j + d) = *newbegin;
			}
		}
	}
}

void selectsort(vector<int>& a)
{
	vector<int>::iterator i = a.begin();
	for (; i < a.end(); i++)
	{
		vector<int>::iterator j = i + 1;
		vector<int>::iterator  min =i;
		for (; j < a.end(); j++)
			if (*j < *min)min = j;
		if (min != i)swap(*min,*i);//i为后子列第一个元素,min为子列中的最小元素,子列内交换

	}

}

void Bigheap_adjust(vector<int>&a,vector<int>::iterator k,vector<int>::iterator high)
{
	
	*a.begin() = *k;
	int bias = k - a.begin();
	vector<int>::iterator i =a.begin()+2*bias;//a.begin()相当于数组下标0
	while (i <=high)
	{
		//k和bias同时修改方可用迭代器实现

		//第一个判断的目的是找左右孩子中最大的孩子
		if (i != high && *i < *(i + 1))
		{	//若i==a,end()则一定没有右子树,故只有i!=a.end()-1才有可能有右子树,才有可能进行i++;
			//如果*i >= *(i + 1)即左孩子大于右孩子那么就不必再向右子树找了,用左孩子和哨兵比较即可
			i++;//左孩子小于根,考察右孩子或右子树
		}
		if (*a.begin() >= *i)break;//i所指为k结点最大的孩子,左右孩子都小于根k(k==i/2),则该元素该放在k位置
		else//k的最大的一个孩子比根大
		{
			//这里不能简单的认为swap(a,b)
			*k = *i;//大的一个孩子i应该放在k位置
			k = i;//在原来k位置的大孩子为顶的子树中继续考察,i指向该子树顶也就是k的大孩子
		}
		bias=i - a.begin();//i指针代数化
		if (2 * bias>a.size()-1)break;
		i = a.begin() + 2 * bias;//形式化
	}
	*k = *a.begin();
}

void Bigheap_build(vector<int>& a,vector<int>::iterator high)
{
	//a.begin()相当于数组下标0
	//大根堆建立
	vector<int>::iterator i = a.begin()+(high-a.begin()) / 2;
	for (; i > a.begin(); i--)
		Bigheap_adjust(a, i,high);

}

void Bigheap_sort(vector<int>& a)
{
	//每一趟将堆顶与待排序序列最后一个元素swap
	for (vector<int>::iterator high = a.end() - 1; high >=a.begin()+2; high--)
	{
		swap(*(a.begin() + 1), *high);
		Bigheap_build(a, high - 1);
	}

}

void smallheap_adjust(vector<int>& a, vector<int>::iterator k, vector<int>::iterator high)
{
	*a.begin() = *k;
	int bias = k - a.begin();
	vector<int>::iterator i = a.begin() + 2 * bias;//a.begin()相当于数组下标0
	while (i <= high )
	{
		//k和bias同时修改方可用迭代器实现

		//第一个判断的目的是找左右孩子中最小的孩子
		if (i != high && *i > *(i + 1))
		{	//若i==a,end()则一定没有右子树,故只有i!=a.end()-1才有可能有右子树,才有可能进行i++;
			//如果*i >= *(i + 1)即左孩子大于右孩子那么就不必再向右子树找了,用左孩子和哨兵比较即可
			i++;//左孩子小于根,考察右孩子或右子树
		}
		if (*a.begin() <= *i)break;//i所指为k结点最小的孩子,左右孩子都大于根k(k==i/2),则该元素该放在k位置
		else//k的最小的一个孩子比根小
		{
			//这里不能简单的认为swap(a,b)
			*k = *i;//小的一个孩子i应该放在k位置
			k = i;//在原来k的小孩子为顶的子树中继续考察,i指向该子树顶也就是k的小孩子
		}
		bias = i - a.begin();//i指针代数化
		if (2 * bias > a.size() - 1)break;
		i = a.begin() + 2 * bias;//形式化
	}
	*k = *a.begin();

}

void smallheap_build(vector<int>& a,vector<int>::iterator high)
{
	//a.begin()相当于数组下标0
	//小根堆建立
	vector<int>::iterator i = a.begin() + a.size() / 2;
	for (; i > a.begin(); i--)
		smallheap_adjust(a, i, high);

	
}

void smallheap_sort(vector<int>& a)
{
	//每一趟将堆顶与待排序序列最后一个元素swap
	for (vector<int>::iterator high = a.end() - 1; high >= a.begin() + 2; high--)
	{
		swap(*(a.begin() + 1), *high);
		smallheap_build(a, high - 1);
	}

}



int main()
{

	//冒泡排序
	//vector<int> a = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> a = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(a.begin(), a.end(), print);
	cout << endl;
	bubble_sort(a);
	cout << "冒泡排序的结果为:";
	for_each(a.begin(), a.end(), print);
	cout << endl;



	//直接插入排序
	//vector<int> b = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> b = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(b.begin(), b.end(), print);
	cout << endl;

	Insertsort(b);
	cout << "直接插入排序的结果为:";
	for_each(b.begin()+1, b.end(),print);
	cout << endl;
	



	//希尔排序
	//vector<int> c = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> c = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(c.begin(), c.end(), print);
	cout << endl;

	//调整,使得算法vector的迭代器,空出begin()之前init_d位置补0,只在begin()+init_d位置到end()-1位置的数据排序,输出时只输出这一部分内容
	int init_d = c.size() / 2;
	for (int i = 0; i < init_d; i++)
		c.push_back(0);
	for (vector<int>::iterator it = c.end() - 1; it>=c.begin()+init_d; it--)
		(*it) = *(it - init_d);
	for (int i = 0; i < init_d; i++)
		*(c.begin() + i) = 0;
	//for_each(a.begin(), a.end(), print);//测试代码,验证已经在序列前插入辅助位置,避免迭代器异常
	shellsort(c,init_d);
	cout << "希尔排序的结果为:";
	for_each(c.begin() + init_d, c.end(), print);
	cout << endl;





	
	//快速排序
	//vector<int> d = { 2,8,7,6,3,3,2,2,1 };//稳定性测试数组
	vector<int> d = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(d.begin(), d.end(), print);
	cout << endl;
	vector<int>::iterator low = d.begin();
	vector<int>::iterator high = d.end() - 1;
	//调整,使得算法vector的迭代器,空出begin()位置补0,只在begin()+1位置到end()-1位置的数据排序,输出时只输出这一部分内容
	d.push_back(0);
	for (vector<int>::iterator it = d.end() - 1; it > d.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(*d.begin()) = 0;
	low = d.begin() + 1;
	high = d.end() - 1;
	//for_each(d.begin() , d.end(), print);//测试代码
	quicksort(d, low, high);
	cout << "快速排序的结果为:";
	for_each(d.begin() + 1, d.end(), print);
	cout << endl;

	//简单选择排序
	vector<int> e = { 49,38,65,97,76,13,27,49 };
	cout << "待排序的数列为:";
	for_each(e.begin(), e.end(), print);
	cout << endl;
	selectsort(e);
	cout << "简单选择排序的结果为:";
	for_each(e.begin(), e.end(), print);
	cout << endl;



	//堆排序
	//由于2k这种数量关系难以显式的用迭代器表现,所以我们在堆排序的实现中直接用数组进行排序
	//但其实也可以作bias的暂存,再对bias*2+a.begin()也能做..只是不直观
	vector<int> f = {53,17,78,9,45,65,87,32 };
	cout << "待排序的数列为:";
	for_each(f.begin(), f.end(), print);
	cout << endl;
	//建立大根堆
	//首先应该调整,使得i从1开始
	f.push_back(0);
	for (vector<int>::iterator it = f.end() - 1; it > f.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(*f.begin()) = 0;//哨兵位置
	//for_each(f.begin(), f.end(), print);//测试代码

	Bigheap_build(f,f.end()-1);
	cout << "建立的大根堆为:";
	for_each(f.begin()+1, f.end(), print);
	cout << endl;

	//基于大根堆建立递增序列
	Bigheap_sort(f);
	cout << "基于大根堆排序的结果为:";
	for_each(f.begin()+1, f.end(), print);
	cout << endl;
 

	建立小根堆
	vector<int> g = { 53,17,78,9,45,65,87,32 };
	cout << "待排序的数列为:";
	for_each(g.begin(), g.end(), print);
	cout << endl;
	//首先应该调整,使得i从1开始
	g.push_back(0);
	for (vector<int>::iterator it = g.end() - 1; it > g.begin(); it--)
	{
		(*it) = *(it - 1);
	}
	(*g.begin()) = 0;//哨兵位置
	//for_each(g.begin(), g.end(), print);//测试代码


	smallheap_build(g,g.end()-1);
	cout << "建立的小根堆为:";
	for_each(g.begin()+1, g.end(), print);
	cout << endl;

	基于小根堆构造递减序列
	smallheap_sort(g);
	cout << "基于小根堆排序的结果为:";
	for_each(g.begin()+1, g.end(), print);
	cout << endl;



	
	system("pause");

	return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值