【小白打基础系列】排序算法之插入、冒泡与快速排序


直接插入法是最简单的排序法,其排序思想如下:
打牌时,每抓一张牌就将其放到对应的顺序位置。

下面是待排记录的类型定义:

#define MAXSIZE 200
typedef int KeyType//关键字类型
typedef struct{
KeyType key;//关键字
InfoType otherinfo;//其他数据项
}RedType;//记录类型
typededf struct{
RedType r[MAXSIZE+1];//r[0]闲置或作为哨兵
int length;//顺序表长度
}SqList;

上述的RedType结构体类型实际上是没必要定义的,实际做题时可以写成如下代码,更方便一点:

#define MAXSIZE 200
typedef int KeyType//关键字类型
typededf struct{
KeyType r[MAXSIZE+1];//r[0]闲置或作为哨兵
int length;//顺序表长度
}SqList;

直接插入排序算法的描述:

void InsertSort(SqList &L)
{
//对顺序表L做直接插入排序
	for(i=2;i<=L.length;i++)
	{
		if(L.r[i]<L.r[i-1])
		{
			L.r[0]=L.r[i];//r[i]暂存到监视哨中
			L.r[i]=L.r[i-1];//r[i-1]后移
			//记录逐个后移,直到找到插入位置
			for(j=i-2;L.r[0]<L.r[j];j--)
				L.r[j+1]=L.r[j];
			L.r[j+1]=L.r[0];
		
		}
	}	
}

常见错误by小胡
1)低位输出随机数。(记录逐个后移,找到插入位置)三行代码的位置放在了if语句外,导致即便低位符合降序,但仍执行代码,致溢出。

直接插入排序算法分析:

  • 最好的情况是记录序列按关键字有序
  • 比较次数n-1;移动次数0
  • 最坏的情况是记录序列按关键字逆序
  • 比较次数(n+2)(n-1)/2;移动次数(n+4)(n-1)/2
  • 平均情况下比较次数和移动次数约为n2/4,时间复杂度为O(N2)
  • 直接插入排序容易实现,是稳定排序
  • 直接插入排序也适用于链式结构,不需要移动
  • 适用于初始基本有序的情况

希尔排序法是直接插入法的优化,它的思想是先将整个排序序列分成若干子序列,并分别进行直接插入排序,当整个记录中的序列基本有序时,再对全体记录进行一次直接插入排序。

希尔排序法的描述:

void ShellInsert(SqList& L, int dk)
{//对顺序表 L 做一趟增量是 dk 的希尔插入排序
	int i, j;
	for (i = dk + 1; i <= L.length; ++i)
	{
		if (L.r[i] < L.r[i - dk]) {
			//需将 L.r[i] 插入有序增量子表
			L.r[0] = L.r[i]; //暂存在L.r[0]
			for (j = i - dk; j > 0 && L.r[0] < L.r[j]; j -= dk)
				L.r[j + dk] = L.r[j];//记录后移
			L.r[j + dk] = L.r[0];//将 r[0] 插入
		}//if
	}//for
}//ShellInsert
void ShellSort(SqList& L, int dt[], int t)
{//按增量序列 dt[0..t-1] 对顺序表 L 作 t 趟希尔排序
	int k;
	for (k = 0; k < t; ++k)
		ShellInsert(L, dt[k]);
}

希尔排序法算法分析:

  • 时间复杂度取决于增量序列,可到 O(N 4/3 ),优于直接插入排序
  • 如何选择最佳 d序列,目前尚未解决,但增量系列应该无公因子,最后一个增量值必须为 1
  • 空间复杂度为 O(1)
  • 记录跳跃移动导致排序方法不稳定
  • 只能用于顺序结构,不能用于链式结构

利用希尔排序解决如下问题:
输入
输入的第一行包含1个正整数n,表示共有n个整数需要参与排序。其中n不超过1000。
第二行包含n个用空格隔开的正整数,表示n个需要排序的整数.
输出
只有1行,包含n个整数,表示从小到大排序完毕的所有整数。
请在每个整数后输出一个空格,并请注意行尾输出换行。
样例输入
10
2 8 4 6 1 10 7 3 5 9
样例输出
1 2 3 4 5 6 7 8 9 10

下面是源码:

#include 
using namespace std;
#define KeyType int
#define MAXSIZE 1000
typedef struct {
	KeyType r[MAXSIZE+1];
	int length;
}SqList;
void ShellInsert(SqList& L, int dk)
{//对顺序表 L 做一趟增量是 dk 的希尔插入排序
	int i, j;
	for (i = dk + 1; i <= L.length; ++i)
	{
		if (L.r[i] < L.r[i - dk]) {
			//需将 L.r[i] 插入有序增量子表
			L.r[0] = L.r[i]; //暂存在L.r[0]
			for (j = i - dk; j > 0 && L.r[0] < L.r[j]; j -= dk)
				L.r[j + dk] = L.r[j];//记录后移
			L.r[j + dk] = L.r[0];//将 r[0] 插入
		}//if
	}//for
}//ShellInsert
void ShellSort(SqList& L, int dt[], int t)
{//按增量序列 dt[0..t-1] 对顺序表 L 作 t 趟希尔排序
	int k;
	for (k = 0; k < t; ++k)
		ShellInsert(L, dt[k]);
}
int main()
{
	int n;
	cin >> n;
	SqList L;
	int dlta[1000];
	L.length = n;
	for (int i = 1; i <= n; i++)
	{
		cin >> L.r[i];
		dlta[i - 1] = n-i;
	}
	ShellSort(L,dlta,n);
	for (int i = 1; i <= n; i++)
		cout << L.r[i] << " ";
	cout << endl;
	return 0;
}

冒泡排序排序法,相邻元素之间进行比较,如果逆序则交换位置。

冒泡排序法的描述:

 void BubbleSort(SqList &L)
 {//对顺序表L做冒泡排序
 	m = L.length − 1;
 	flag = 1;//用来标记某一趟排序是否发生交换
 	while((m > 0) && (flag == 1)) {
 		flag = 0; //没有发生交换
 		for(j = 1; j <= m; j++)
 			if(L.r[j].key > L.r[j + 1].key) {
 				flag = 1;//表示本趟排序发生了交换
 				t = L.r[j];
 				L.r[j] = L.r[j + 1];
 				L.r[j + 1] = t;//交换前后两个记录
 			}//if
 		−−m;
 	}//while
}//BubbleSort

实际上此冒泡排序是对原冒泡排序的一种优化,flag标识符实现了优化。
例如:1 2 5 3 7 8 9 10
原冒泡排序要比较7趟,而此冒泡排序只需比较3趟。

下面贴上原冒泡排序算法描述:

void BubbleSort(SqList &L)
{
    int i, j,temp;
    int m=L.length-1;
    while(m>0)
        for (j = 1; j <= m; j++)
        	if (L.r[j] > L.r[j + 1])
        	{
            	temp = L.r[j];
            	L.r[j] = L.r[j + 1];
            	L.r[j + 1] = temp;
        	}
        --m;
}

冒泡排序算法分析:

  • 最好情况下初始有序,只需一趟排序,比较次数为n-1,不需要移动记录
  • 最坏情况是初始逆序,需 n-1 趟排序
  • 比较的次数:n(n-1)/2;移动的次数:3n(n-1)/2
  • 平均时间复杂度为 O(n 2 )
  • 辅助空间复杂度为 O(1)
  • 是一种稳定的排序方法,可用于链式存储结构

快速排序法是最常用的一种排序方法,同时也是很典型的分治思想,分而治之。它的排序思路如下:

1.任取一个记录作为枢轴
2.划分:所有比枢轴小的记录一律前放,比枢轴大的记录一律后放,形成左右两个子表
3.对子表重新进行前两步操作,直到每个子表元素只剩一个

快速排序法算法描述:

int Partition(SqList &L,int low,int high)
{//对表 r[low..high] 进行一趟排序,返回枢轴位置
	L.r[0]=L.r[low]; //子表的第一个记录做枢轴
	pivotkey=L.r[low];
	while(low<high){//从表两端交替向中间扫描
		while(low<high&&L.r[high]>=pivotkey)
			−−high;
	L.r[low]=L.r[high];//比枢轴小的移到低端
 	while(low<high&&L.r[low]<=pivotkey)
 		++low;
 	L.r[high]=L.r[low];//比枢轴大的移到高端
 }//while
 	L.r[low]=L.r[0]; //枢轴记录到位
 	return low; //返回枢轴位置
}//Partition
 void QSort(SqList &L, int low, int high)
 { //对顺序表 L 中的子序列 L.r[low..high] 做快速排序
 	if(low < high) { //长度大于1
 		pivotloc = Partition(L, low, high);
 		QSort(L, low, pivotloc − 1);//左子表排序
 		QSort(L, pivotloc + 1, high);//右子表排序
 	}//if
 }//QSort
 void QuickSort(SqList &L)
 { //对顺序表L做快速排序
 	QSort(L, 1, L.length);
 }//QuickSort

快速排序算法分析:

  • 性能最好情况是均匀划分,时间复杂度是 O(nlog 2 n)。
  • 性能最差情况如初始序列有序,导致最不均匀划分,性能退至O(n 2)。
  • 为避免最坏情况发生,可以三者取中,即头、尾和中间记录三者中关键字居中的记录作为枢轴
  • 可以证明,平均计算时间是 O(nlog n)。
  • 实验结果表明:就平均计算时间而言,快速排序是所有内部排序方法中最好的一个。
  • 辅助空间复杂度取决于栈的层次,最坏 O(n), 最好O(log 2 n)。
  • 快速排序不稳定,不适用于链表结构。

你可能感兴趣的:(【小白打基础系列】排序算法之插入、冒泡与快速排序)