数据结构之排序

介绍排序

排序:根据元素关键字的大小或其他规则来对序列中的元素进行调整,以确保它们的相对顺序符合所需的排序顺序(升序或降序)。

排序的稳定性:如果关键字key1=key2,在排序后记录1和记录2的位置没有发生变化,那么这种排序方法就是稳定的,相反,如果排序后记录1和2的顺序和排序前发生了变化,则此排序方法不稳定。

内排序:在排序的过程中,待排序的所有记录放置在内存中。

外排序:当数据无法一次性全部加载到内存中时,将大文件分割成适当大小的块,然后在内存中对这些块进行排序,最后将排好序的块合并成一个有序的文件。这个过程通常需要从外部存储(磁盘)读取和写入大量数据。

内排序又分为:插入排序、交换排序、选择排序、递归排序

排序准备

此篇文章的所有排序操作均是对以下顺序表进行排序操作。

结构如下:

typedef struct{
	int a[MAXSIZE+1];//加了一个哨兵位置 
	int length;//表长(元素个数)
}SqList;

交换函数:

void swap(SqList *L,int i,int j){
	int temp;
	temp=L->a[i];
	L->a[i]=L->a[j];
	L->a[j]=temp;
}

冒泡排序

传统冒泡排序

如果不知道冒泡排序的同学,详细介绍请看下面文章:

写文章-CSDN创作中心icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/134959929

冒泡排序优化

看了以上文章你应该知道,这种排序方法不管在什么情况下都会进行n-1趟排列。但是当一个序列只有前几个是无序的,后几个都是满足你规定的顺序,那这样做就显得很不聪,根本没有必要做那么多趟排序。

具体优化见下面代码:

void BubbleSort(SqList *L){
	int i,j;
	bool flag=true;//标记变量 
	for(i=0;i<L->length -1&&flag;i++){
		flag=false;
		for(j=0;j<L->length -1-i;j++){//每一趟最后一个元素都已经确定为最大的,所以每次不用比较最后的元素,这里减掉趟数 
			if(L->a[j]>L->a[j+1]){
				swap(L,j,j+1);
				flag=true;//如果存在元素交换则将标记变量赋值为真。当不存在交换,就说明此序列已经有序,flag=false,结束循环 
			}
		}
	}
}

进行优化后,只有序列为逆序的情况下才会进行n-1次,其他情况均小于n-1次。

分析

(以下对优化后的算法进行分析)

当序列有序时,进行n-1次比较,只进行一趟排序;

当序列逆序时,每一趟进行(n-i)次比较,总共进行n(n-1)/2次比较,进行n-1趟排序;

时间复杂度:O(n^{2})

简单选择排序

简单选择排序:在未排序的部分中选择最小的元素,然后将其与未排序部分的第一个元素交换位置。这个过程不断重复,每次选择未排序部分的最小元素,直到整个序列有序。

代码如下:

void SelectSort(SqList *L){
	int i,j;
	int min;
	for(i=0;i<L->length;i++){
		min=i;
		for(j=i+1;j<L->length;j++){
			if(L->a[min]>L->a [j]){
				 min=j;
			}
		}
		if(i!=min);
		swap(L,min,i);
	}
}

分析

优点:交换的次数少。

不论什么情况,每一趟进行(n-i)次比较,总共进行n(n-1)/2次比较。

最少交换0次,最多交换n-1次。

时间复杂度:O(n^{2})

直接插如排序

将数组分为已排序和未排序两部分,初始时已排序部分只有一个元素。算法从未排序部分逐个取出元素,插入到已排序部分的正确位置,通过逐步构建有序序列实现整体排序。在每一步,当前元素与已排序部分的元素逐个比较,找到其正确插入位置,确保已排序部分仍然有序。这一过程重复进行直到整个数组有序。

代码如下:

void InsertSort(SqList *L){
	int i,j,temp;
	for(i=1;i<L->length ;i++){// 循环从第二个元素开始,因为第一个元素默认是有序的
		j=i-1;
		if(L->a [i]<L->a [j]){
			temp=L->a[i];
			while(j>=0&&temp<L->a[j]){
				L->a[j+1]=L->a[j];
				j--;
			}
			L->a[j+1]=temp;// 将 temp 插入到找到的合适位置,j+1 是因为在 while 循环中 j 被减了一次
		}
	}
} 

分析

序列是有序是需要比较(n-1)次,移动零次

序列为逆序时,

对于每个元素,都需要移动到已排序部分的最前面。因此,第二个元素需要移动一次,第三个元素需要移动两次,第四个元素需要移动三次,以此类推。

总的移动次数是前 n 个整数的累加和:n(n-1)/2

需要移动:n(n-1)/2

时间复杂度:O(n^{2})

希尔排序

希尔排序:希尔排序是一种基于插入排序的排序算法,它的特点在于通过预处理使得原数组中某些较远的元素先有序,然后逐步缩小间隔,最终实现整体有序。

思想:希尔排序的核心思想是通过每一轮的插入排序,减小数组中元素的距离,使得原本较远的元素有机会在前几轮中先有序。

算法步骤:

  1. 选择一个增量序列(间隔序列):通常使用长度/2。
  2. 对每个增量序列进行排序:对于每个增量,将数组分成若干个子序列,每个子序列包含相邻间隔为增量的元素。对每个子序列应用插入排序。
  3. 减小增量:每一次减小增量/2。重复上述过程,缩小增量,直到增量为1。最终一次使用增量为1的插入排序,完成整个排序过程。

分析

堆排序

大顶堆:完全二叉树每个结点的值都大于或等于其左右孩子结点的值。

小顶堆:完全二叉树每个结点的值都小于或等于其左右孩子结点的值。

在堆排序算法中,第一步是构建一个初始的大顶堆,然后将堆顶元素(最大值)与堆中最后一个元素交换,接着对剩余的堆进行调整,确保仍然保持大顶堆的性质。这样的交换和调整步骤会重复进行,直到整个序列有序。

归并排序

归并排序的初始阶段将原始序列中的每个元素看作一个有序队列(长度为1的队列)。随后,逐步将相邻的有序队列两两合并,并对合并后的队列进行排序。然后,将这些排好序的队列再次两两合并,不断重复这一步骤,直到合并后的队列长度达到原始序列的长度,此时排序完成。这个过程保证了每次合并都是在有序的基础上进行,最终得到整个序列的有序结果。

快速排序

基本思想:

  1. 选择基准值:从待排序的序列中选择一个元素作为基准值。(为了方便选择第一个元素作为基准值)
  2. 划分序列:将比基准值小的放在基准值的左边,将大于基准值的放在基准值的右边。一趟排序下来基准值左边的值均小于右边的值。再次递归对基准值左边和右边的序列进行以上操作,直到当低指针low大于等于高指针high时,表示当前子序列的长度为1或0,即已经有序,此时递归结束。

代码如下:

//快排关键函数
int Partition(SqList *L,int low,int high){
	int pivotkey=L->a [low];//将第一个值确定为基准值 
	while(low<high){
		while(low<high&&L->a[high]>pivotkey){
			high--;
		}
		swap(L,low,high);
		while(low<high&&L->a[low]<pivotkey){
			low++;
		}
		swap(L,low,high);
	}
	return low;//返回基准值的位置 
} 

//快排递归函数
void QSort(SqList *L,int low,int high){
	int pivot;
    if (low < high) {//递归条件,直到当低指针low大于等于高指针high时,表示当前子序列的长度为1或0,即已经有序 
        pivot = Partition(L, low, high);
        QSort(L, low, pivot - 1);//对基准值左侧进行排序 
        QSort(L, pivot + 1, high);//对基准值右侧进行排序 
    }
}

//快排函数
void QuickSort(SqList *L){
	QSort(L,0,L->length-1 );
}

快速排序优化

优化基准值

三数取中

选择中间位置的元素作为基准值,通过比较三个位置的元素,确保基准值在三者中间,有助于提高基准值的选择的准确性,减小最坏情况的概率,从而提高整体排序性能。

代码如下:

	int mid=(low+high)/2;
	if(L->a [low]>L->a [high])
	swap(L,low,high);
	if(L->a [mid]>L->a [high])
	swap(L,mid,high);
	if(L->a [mid]>L->a [low])
	swap(L,low,mid); 
	
	int pivotkey=L->a [low];

结合插入排序优化性能

当子序列的规模较小时,切换到插入排序,因为对于小规模数据,插入排序更为高效。插入排序在小规模数据上有较好的性能,而快速排序在大规模数据上表现更好,因此这种切换可以提高整体性能。

代码如下:

#define THRESHOLD 7  //设置一个阈值,当序列长度小于该阈值时使用插入排序,反之,则使用快速排序 
void QSort(SqList *L,int low,int high){
	int pivot;
    if (high-low>THRESHOLD) {
        pivot = Partition(L, low, high);
        QSort(L, low, pivot - 1);//对基准值左侧进行排序 
        QSort(L, pivot + 1, high);//对基准值右侧进行排序 
    }
    else
    InsertSort(L);
}

 优化递归

代码如下:

#define THRESHOLD 3 
void QSort(SqList *L,int low,int high){
	int pivot;
    if (high-low>THRESHOLD) {

//优化递归----------------------------------------------------------------------------------
    	while(low<high){
    	pivot = Partition(L, low, high);
        QSort(L, low, pivot - 1); 
        low=pivot+1;
		}
//-----------------------------------------------------------------------------------------  
    }
    else
    InsertSort(L);
}

减少交换

将交换操作改变为替换操作(简单的赋值语句:L->a [low]=L->a [high];),减少交换次数优化性能。

代码如下:

while(low<high){
		while(low<high&&L->a[high]>pivotkey){
			high--;
		}
		L->a [low]=L->a [high];//减少交换次数 
		while(low<high&&L->a[low]<pivotkey){
			low++;
		}
		L->a [high]=L->a [low] ;//减少交换次数 
	}
	L->a [low]=temp;

优化后的完整代码如下:

//交换操作函数 
void swap(SqList *L,int i,int j){
	int temp;
	temp=L->a[i];
	L->a[i]=L->a[j];
	L->a[j]=temp;
}

//快排关键函数	
int Partition(SqList *L,int low,int high){
//三数取中优化基准值 ------------------------------------------------------ 
	int mid=(low+high)/2;
	if(L->a [low]>L->a [high])
	swap(L,low,high);
	if(L->a [mid]>L->a [high])
	swap(L,mid,high);
	if(L->a [mid]>L->a [low])
	swap(L,low,mid); 
//-------------------------------------------------------------------------	
	int pivotkey=L->a [low];//将第一个值确定为基准值
	int temp=L->a[low]; 
	while(low<high){
		while(low<high&&L->a[high]>pivotkey){
			high--;
		}
		L->a [low]=L->a [high];//减少交换次数 
		while(low<high&&L->a[low]<pivotkey){
			low++;
		}
		L->a [high]=L->a [low] ;//减少交换次数 
	}
	L->a [low]=temp;
	return low;//返回基准值的位置 
} 

//快排递归函数
#define THRESHOLD 3  //设置一个阈值,当序列长度小于该阈值时使用插入排序,反之,则使用快速排序 
void QSort(SqList *L,int low,int high){
	int pivot;
    if (high-low>THRESHOLD) {
//优化递归 -------------------------------------------------------------------------------
    	while(low<high){
    	pivot = Partition(L, low, high);
        QSort(L, low, pivot - 1);
        low=pivot+1;
		}  
//---------------------------------------------------------------------------------------
    }
    else
    InsertSort(L);
}

//快排函数
void QuickSort(SqList *L){
	QSort(L,0,L->length-1 );
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值