基本的几种排序算法详解

目录

排序的定义

插入排序

折半插入排序

希尔排序

冒泡排序:

快速排序

选择排序

基本选择排序

堆排序

归并排序


排序的定义

排序是将一个集合按照元素关键字的大小重新排列成有序序列的操作。主要目的是便于查找和筛选。

插入排序

通过将未排序部分的元素逐个按其关键字大小插入已排序部分的恰当位置,最终实现全部元素有序排列。

算法思想

首先,将原始一个无序表看成两个子表,前一个子表为有序表,后一个子表为无序表,开始将第一个元素看为有序子表,其余元素为无序子表;

每趟排序按照关键字的大小将无序表的一个元插入到有序表的恰当位置;

直至整个数据表有序。

void InsertSort(SqList *L)	//升序 
{
	int i,j;
	for(i=2;i<=L->len;i++)	//从第二个元素开始一一插入到前面 
	{
		L->list[0]=L->list[i];	//监视哨 
		j=i-1;
		while(L->list[0].key<L->list[j].key)	//从有序表后往前查找插入位置 
		{
			L->list[j+1]=L->list[j];
			j--;    //继续向前查找
		}
		L->list[j+1]=L->list[0];    //插入
	}	
}

示例

算法分析:

稳定性:稳定的。

空间复杂度:O(1)。

时间复杂度:最坏的情况为O(n*n),最好的情况为O(n)。

L->list[0]有两个作用:一是保存待插入元素L->list[i]的值,一面在后移过程中被覆盖丢失;二是作为“监视哨”。

折半插入排序

折半插入排序又称为二分插入排序,是对直接插入排序的一种改进方法。

算法思想

和直接插入排序思想类似。

void BinarySort(SqList* L)    //升序
{
	cout << "调用折半插入排序" << endl;
	int i, j, low, high, mid;
	for (i = 2; i <= L->len; i++)
	{
		L->list[0] = L->list[i];
		low = 0;
		high = i - 1;
		while (low <= high)
		{
			mid = (low + high) / 2;
			if (L->list[0].key < L->list[mid].key)
				high = mid - 1;
			else
				low = mid + 1;
		}	//折半查找结束,位置为low 
		for (j = i - 1; j >= low; j--)	//元素一一后移 
			L->list[j + 1] = L->list[j];
		L->list[low] = L->list[0];
	}

算法分析:

稳定性:稳定的。

空间复杂度:O(1)。

时间复杂度:O(n*n)。

注意:由于在有序表中查找元素插入位置时采用二分查找实现,折半插入排序只能应用于顺序存储结构。

希尔排序

设计思路:

直接插入排序在元素个数较少且基本有序时,效率很高。所以如果能实现1>减少待排序数据表元素个数,2>排序的数据表基本有序,则可以排序效率提高。

算法思想:

先取一个正整数d1(d1<n)作为第一个增量,将全部n个记录分成d1个组,把所有距离为d1倍数的记录放在同一组中,在各组内进行直接插入排序。这样一次分组和排序过程称为一趟希尔排序;

取新的增量d2<d1,重复1) 的分组和排序操作:直至所取的增量di=1为止,即所有记录放进一个组中排序为止。

示例:

设待排序记录关键字分别是 39 80 76 41 13 29 50 78 30 11 100 7 41 86 。增量序列取值依次为 5 3 1

void ShellSort(SqList* L, int d[])	//d数组存放每趟的增量 
{
	cout << "调用希尔排序" << endl;
	int i, j, k, h;
	k = 0;
	do
	{
		h = d[k];	//取增量 
		for (int i = h + 1; i <= L->len; i++)	//i为待查热元素的下标 
		{
			L->list[0] = L->list[i];
			j = i - h;	//查看待插入元素的前一个元素 
			while (L->list[0].key < L->list[j].key && j>0)
			{
				L->list[j + h] = L->list[j];
				j = j - h;
			}
			L->list[j + h] = L->list[0];
		}
		k++;
	} while (h != 1);	//当增量为1时结束 
}

 算法分析:

稳定性:不稳定。

空间复杂度:依赖于步长银子序列的选取。介于O(nlog2(n))和O(n*n)之间。

冒泡排序:

算法思想:

将待排序的n个记录看作按纵向排列,每趟排序时自下至上对排序范围中每对相邻元素进行比较,若次序不符合要求就交换。
每趟排序都能使待排序范围内关键字最小的元素像气泡一样“冒”到待排序范围最上端。
void BubbleSort(SqList* L)
{
	cout << "调用冒泡排序" << endl;
	int i, j, k, n = L->len;
	int t;
	for (i = 1; i < n; i++)
		for (j = n; j > i; j--)
			if (L->list[j].key < L->list[j - 1].key)
			{
				t = L->list[j].key;
				L->list[j].key = L->list[j - 1].key;
				L->list[j - 1].key = t;
			}
}

示例

从图中可以看出,第四趟已经全部有序了,但是算法并没有提前结束。

解决方案:

在冒泡排序算法中加入一个flag标志变量控制在数据表已经有序的情况下提前结束排序。

flag=1,表名数据表无序,下一趟排序仍需进行。

flag=0,表明数据表有序,排序过程应结束。

void BubbleSort_1(SqList* L)
{
	cout << "调用冒泡函数优化版" << endl;
	int i, j, k, n = L->len, flag;
	DataType t;
	i = 1;
	flag = 1;
	while ((i < n) && (flag == 1))
	{
		for (j = n; j > i; j--)
			if (L->list[j].key < L->list[j - 1].key)
			{
				flag = 1;
				t = L->list[j];
				L->list[j] = L->list[j - 1];
				L->list[j - 1] = t;
			}
		i++;
	}
}

算法分析

时间复杂度:O(n*n)。

空间复杂度:O(1)。

稳定性:稳定的

快速排序

算法思想:

任取待排序序列中的某个元素作为基准(一般取第一个元素),将待排序元素分为左右两个子表,左子表中元素的关键字值均小于或等于基准元素的关键字值,右子表中元素的关键字值均大于或等于基准元素的关键字值;
对得到的子表继续划分,直至每个子表只有一个元素或为空为止。
int QuickPass(SqList* L, int low, int high)
{
	int i, j;
	i = low;    //i从最左端开始
	j = high;   //j从最右端开始
	L->list[0] = L->list[i];    //L->list[0]存放基准元素
	while (i != j)    //重复左右交替扫描,直至i和j重合
	{
		while ((L->list[j].key >= L->list[0].key) && (i < j))    //从右向左扫描
			j--;
		if (i < j)    //若发现小于基准关键字的元素,将其放在左子表中并改变扫描方向
		{
			L->list[i] = L->list[j];
			i++;
		}
		while ((L->list[i].key <= L->list[0].key) && (i < j))    //从左向右扫描
			i++;
		if (i < j)    //若发现大于基准关键字的元素,将其放在右子表中并改变扫描方向
		{
			L->list[j] = L->list[i];
			j--;
		}
	}
	L->list[i] = L->list[0];
	return i;    //返回划分点位置
}
void QuickSort(SqList* L, int s, int t)
{
	cout << "调用快速排序" << endl;
	int i;
	if (s < t)    //只要排序区间中的元素超过1个,仍然继续进行快排
	{
		i = QuickPass(L, s, t);    //对范围进行一次划分
		QuickSort(L, s, i - 1);    //递归调用,分别对划分得到的两个子表进行快速排序
		QuickSort(L, i + 1, t);
	}
}

算法分析

空间复杂度:最好O(log2(n)),最坏O(n)。

时间复杂度:最好O(nlog2(n)),最坏O(n*n)。平均情况:O(nlog2(n))。

稳定性:不稳定。

选择排序

基本选择排序

算法思想:

第一趟排序时从待排序的n个记录中选择出关键字最小(大)的记录,将其与第1个记录交换;
第二趟,从第二个记录开始的n-1个记录中选择出关键字最小(大)的记录,将其与第2个记录交换;
如此下去,通过n-1趟操作,整个数据表就排列有序。
void SelectSort(SqList* L)
{
	cout << "调用选择排序" << endl;
	int i, j, k, n;
	DataType temp;
	n = L->len;
	for (i = 1; i <= n - 1; i++)
	{
		k = i;	//记录当前最小元素的下标 
		for (j = i + 1; j <= n; j++)	//寻找待排序范围的最小元素 
			if (L->list[j].key < L->list[k].key)
				k = j;
		if (k != j)	//将待排序范围内找到的最小元素与表前端对应位置上的元素进行交换 
		{
			temp = L->list[i];
			L->list[i] = L->list[k];
			L->list[k] = temp;
		}
	}
}

示例

时间复杂度:O(1)。

空间复杂度:O(n*n)。

稳定性:不稳定。

其性能与初始状态无关。 

堆排序

堆排序局势二叉树形选择排序法。堆分为大根堆和小根堆。

若有n个元素的排序码k1,k2,...,kn,满足如下条件:

大根堆和小根堆

调整大根堆的原理和过程如下示例

堆调整函数(用于创建初始堆及重新调整堆)

void HeapAdjust(SeqList *L,int low,int high)
{
	int i,j;
	i=low;
	j=2*i;
	L->list[0]=L->list[i]; 
	for(;j<=high;j*=2)	//自顶向下对节点进行调整 
	{
		if(j<high&&L->list[j].key<L->list[j+1].key)	//找出i号结点左右孩子较大的一个 
			j++;
		if(L->list[0].key>=L->list[j].key)	//j号结点与其双亲进行比较
			break;
		else
		{
			L->list[i]=L->list[j];
			i=j;	//j这个位置成为新的待定的,调整继续向下层结点进行 
		} 
	}
	L->list[i]=L->list[0];	//把最初被调整的结点放在合适的位置 
} 

堆创建函数(堆创建的过程的过程就是,就是反复调用HeapAdjust函数,从后从前依次将以每个非叶子结点为根节点的子树调整为大顶堆的过程)

void HeapCreate(SeqList *L)
{
	int i,n;
	n=L->len;
	for(i=n/2;i>0;i--)
	{
		HeapAdjust(L,i,n);
	}
} 

堆排序

void HeapSort(SqList* L)
{
	cout << "调用堆排序" << endl;
	int i, n = L->len;
	HeapCreate(L);    //创建最大堆
	for (i = n; i > 1; i--)
	{
		L->list[0] = L->list[1];
		L->list[1] = L->list[i];
		L->list[i] = L->list[0];
		HeapAdjust(L, 1, i - 1);    //将表前面i-1个元素重新调整为堆
	}
}

算法分析:

空间复杂度:O(1)。

时间复杂度:O(nlog2(n))。

稳定性:不稳定。

归并排序

算法思想:

首先将待排序的n个元素看作n个长度为1的有序子表,然后从第一个子表开始,对相邻的子表进行两两合并;

接着在对合并后的有序子表继续进行两两合并,重复以上过程,直至得到一长度为n的有序表。

void Merge(SeqList *sr,int s,int m,int t,SeqList *tr)
{
	int i,j,k;
	i=s;	//i保存sr前面子表当前元素位置
	j=m+1;	//j保存后面子表中当前元素位置
	k=s;	///k保存归并后tr对应的顺序表中的当前元素位置
	while((i<=m)&&(j<=t)	//若前后两个子表中的元素均未全部合并
	{
		if(sr->list[i].key<=sr->list[j].key)	//两个子表中关键字较小元素存入tr中
			tr->list[k]=sr->list[i++];
		else
			tr->list[k]=sr->list[j++];
		k++; 
	}
	while(i<=m)	//将sr所对应的前面子表中的剩余元素放入tr对应的顺序表中
		tr->list[k++]=sr->list[i++];
	while(j<=t)	 //将sr所对应的后面子表中的剩余元素放入tr对应的顺序表中
		tr->list[k++]=sr->list[j++];
}
void MergeSort(SqList* sr, SqList* tr, int s, int t)
{	//sr,tr分别指向排序前后的顺序表;s和t分别用于存放sr所指向的待排序顺序表区间的左右端点 
	cout << "调用堆排序" << endl;
	int m;
	SqList temp;	//temp为暂存的辅助顺序表 
	if (s == t)	//若到排序区间仅有一个元素 
		tr->list[s] = sr->list[s];
	else
	{
		m = (s + t) / 2;	//将排序区间一分为二,m保存中间位置 
		MergeSort(sr, &temp, s, m);	//递归调用对前面的子表进行归并排序 
		MergeSort(sr, &temp, m + 1, t);	//递归调用堆后面的子表进行归并排序 
		Merge(&temp, s, m, t, tr);	//堆前后两个表进行合并 
	}
	tr->len = sr->len;
}

算法分析:

时间复杂度:O(nlog2(n))。

空间复杂度:O(n)。

稳定性:稳定的。

 最后给出测试代码

#include<iostream>
using namespace std;
#define MAXLEN 1000
typedef struct
{
	int key;
}DataType;
typedef struct
{
	int len;
	DataType list[MAXLEN+1];
} SqList;
void InsertSort(SqList* L)	//升序 
{
	cout << "调用直接插入排序" << endl;
	int i, j;
	for (i = 2; i <= L->len; i++)	//从第二个元素开始一一插入到前面 
	{
		L->list[0] = L->list[i];	//监视哨 
		j = i - 1;
		while (L->list[0].key < L->list[j].key)	//从有序表后往前查找插入位置 
		{
			L->list[j + 1] = L->list[j];
			j--;    //继续向前查找
		}
		L->list[j + 1] = L->list[0];    //插入
	}
}
void InitSqList(SqList* L)
{
	//cout << "请输入元素个数" << endl;
	//int x;
	//cin >> x;
	//L->len = x;
	//while (x != 0)
	//	cin >> L->list[x--].key;
	L->len = 8;
	L->list[1].key = 15;
	L->list[2].key = 47;
	L->list[3].key = 26;
	L->list[4].key = 58;
	L->list[5].key = 25;
	L->list[6].key = 29;
	L->list[7].key = 50;
	L->list[8].key = 99;
	
}
void Print(SqList& L)
{
	int i = 1;
	while (i <= L.len)
	{
		cout << L.list[i++].key << " ";
	}
	cout << endl;
}
void BinarySort(SqList* L)    //升序
{
	cout << "调用折半插入排序" << endl;
	int i, j, low, high, mid;
	for (i = 2; i <= L->len; i++)
	{
		L->list[0] = L->list[i];
		low = 0;
		high = i - 1;
		while (low <= high)
		{
			mid = (low + high) / 2;
			if (L->list[0].key < L->list[mid].key)
				high = mid - 1;
			else
				low = mid + 1;
		}	//折半查找结束,位置为low 
		for (j = i - 1; j >= low; j--)	//元素一一后移 
			L->list[j + 1] = L->list[j];
		L->list[low] = L->list[0];
	}
}

void ShellSort(SqList* L, int d[])	//d数组存放每趟的增量 
{
	cout << "调用希尔排序" << endl;
	int i, j, k, h;
	k = 0;
	do
	{
		h = d[k];	//取增量 
		for (int i = h + 1; i <= L->len; i++)	//i为待查热元素的下标 
		{
			L->list[0] = L->list[i];
			j = i - h;	//查看待插入元素的前一个元素 
			while (L->list[0].key < L->list[j].key && j>0)
			{
				L->list[j + h] = L->list[j];
				j = j - h;
			}
			L->list[j + h] = L->list[0];
		}
		k++;
	} while (h != 1);	//当增量为1时结束 
}

void BubbleSort(SqList* L)
{
	cout << "调用冒泡排序" << endl;
	int i, j, k, n = L->len;
	DataType t;
	for (i = 1; i < n; i++)
		for (j = n; j > i; j--)
			if (L->list[j].key < L->list[j - 1].key)
			{
				t = L->list[j];
				L->list[j] = L->list[j - 1];
				L->list[j - 1] = t;
			}
}
void BubbleSort_1(SqList* L)
{
	cout << "调用冒泡函数优化版" << endl;
	int i, j, k, n = L->len, flag;
	DataType t;
	i = 1;
	flag = 1;
	while ((i < n) && (flag == 1))
	{
		for (j = n; j > i; j--)
			if (L->list[j].key < L->list[j - 1].key)
			{
				flag = 1;
				t = L->list[j];
				L->list[j] = L->list[j - 1];
				L->list[j - 1] = t;
			}
		i++;
	}
}
int QuickPass(SqList* L, int low, int high)
{
	int i, j;
	i = low;    //i从最左端开始
	j = high;   //j从最右端开始
	L->list[0] = L->list[i];    //L->list[0]存放基准元素
	while (i != j)    //重复左右交替扫描,直至i和j重合
	{
		while ((L->list[j].key >= L->list[0].key) && (i < j))    //从右向左扫描
			j--;
		if (i < j)    //若发现小于基准关键字的元素,将其放在左子表中并改变扫描方向
		{
			L->list[i] = L->list[j];
			i++;
		}
		while ((L->list[i].key <= L->list[0].key) && (i < j))    //从左向右扫描
			i++;
		if (i < j)    //若发现大于基准关键字的元素,将其放在右子表中并改变扫描方向
		{
			L->list[j] = L->list[i];
			j--;
		}
	}
	L->list[i] = L->list[0];
	return i;    //返回划分点位置
}
void QuickSort(SqList* L, int s, int t)
{
	cout << "调用快速排序" << endl;
	int i;
	if (s < t)    //只要排序区间中的元素超过1个,仍然继续进行快排
	{
		i = QuickPass(L, s, t);    //对范围进行一次划分
		QuickSort(L, s, i - 1);    //递归调用,分别对划分得到的两个子表进行快速排序
		QuickSort(L, i + 1, t);
	}
}
void SelectSort(SqList* L)
{
	cout << "调用选择排序" << endl;
	int i, j, k, n;
	DataType temp;
	n = L->len;
	for (i = 1; i <= n - 1; i++)
	{
		k = i;	//记录当前最小元素的下标 
		for (j = i + 1; j <= n; j++)	//寻找待排序范围的最小元素 
			if (L->list[j].key < L->list[k].key)
				k = j;
		if (k != j)	//将待排序范围内找到的最小元素与表前端对应位置上的元素进行交换 
		{
			temp = L->list[i];
			L->list[i] = L->list[k];
			L->list[k] = temp;
		}
	}
}
void HeapAdjust(SqList* L, int low, int high)
{
	int i, j;
	i = low;
	j = 2 * i;
	L->list[0] = L->list[i];
	for (; j <= high; j *= 2)	//自顶向下对节点进行调整 
	{
		if (j < high && L->list[j].key < L->list[j + 1].key)	//找出i号结点左右孩子较大的一个 
			j++;
		if (L->list[0].key >= L->list[j].key)	//j号结点与其双亲进行比较
			break;
		else
		{
			L->list[i] = L->list[j];
			i = j;	//j这个位置成为新的待定的,调整继续向下层结点进行 
		}
	}
	L->list[i] = L->list[0];	//把最初被调整的结点放在合适的位置 
}
void HeapCreate(SqList* L)
{
	int i, n;
	n = L->len;
	for (i = n / 2; i > 0; i--)
	{
		HeapAdjust(L, i, n);
	}
}
void HeapSort(SqList* L)
{
	cout << "调用堆排序" << endl;
	int i, n = L->len;
	HeapCreate(L);    //创建最大堆
	for (i = n; i > 1; i--)
	{
		L->list[0] = L->list[1];
		L->list[1] = L->list[i];
		L->list[i] = L->list[0];
		HeapAdjust(L, 1, i - 1);    //将表前面i-1个元素重新调整为堆
	}
}
void Merge(SqList* sr, int s, int m, int t, SqList* tr)
{
	int i, j, k;
	i = s;	//i保存sr前面子表当前元素位置
	j = m + 1;	//j保存后面子表中当前元素位置
	k = s;	///k保存归并后tr对应的顺序表中的当前元素位置
	while ((i <= m) && (j <= t))	//若前后两个子表中的元素均未全部合并
	{
		if (sr->list[i].key <= sr->list[j].key)	//两个子表中关键字较小元素存入tr中
			tr->list[k] = sr->list[i++];
		else
			tr->list[k] = sr->list[j++];
		k++;
	}
	while (i <= m)	//将sr所对应的前面子表中的剩余元素放入tr对应的顺序表中
		tr->list[k++] = sr->list[i++];
	while (j <= t)	 //将sr所对应的后面子表中的剩余元素放入tr对应的顺序表中
		tr->list[k++] = sr->list[j++];
}
void MergeSort(SqList* sr, SqList* tr, int s, int t)
{	//sr,tr分别指向排序前后的顺序表;s和t分别用于存放sr所指向的待排序顺序表区间的左右端点 
	cout << "调用堆排序" << endl;
	int m;
	SqList temp;	//temp为暂存的辅助顺序表 
	if (s == t)	//若到排序区间仅有一个元素 
		tr->list[s] = sr->list[s];
	else
	{
		m = (s + t) / 2;	//将排序区间一分为二,m保存中间位置 
		MergeSort(sr, &temp, s, m);	//递归调用对前面的子表进行归并排序 
		MergeSort(sr, &temp, m + 1, t);	//递归调用堆后面的子表进行归并排序 
		Merge(&temp, s, m, t, tr);	//堆前后两个表进行合并 
	}
	tr->len = sr->len;
}
int main()
{
	SqList L;
	InitSqList(&L);
	// ===插入排序
	//InsertSort(&L);
	// ===折半插入排序
	//BinarySort(&L);
	// ===希尔排序
	//int d[3] = { 5,3,1 };
	//ShellSort(&L, d);
	// ===冒泡排序
	//BubbleSort(&L);
	// ===冒泡排序优化
	//BubbleSort_1(&L);
	// ===快速排序
	//QuickSort(&L, 1, L.len);
	// ===选择排序
	//SelectSort(&L);
	// ===堆排序
	//HeapSort(&L);
	//SqList LL;
	//MergeSort(&L, &LL, 0, L.len);
	//Print(LL);
	//Print(L);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小谢%同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值