数据结构 内排序

内排序就是在内存里排序。外排序涉及内、外存间的数据交换。

一 交换排序

1 冒泡排序

  • 时间复杂度O(n^2)
  • 辅助空间复杂度O(1)(就地排序)
  • 稳定(相等的元素排序前后相对位置不变)
  • 每次排序有元素归位(homing)
void BubbleSort(RecType R[], int n)
{
	for(int i=0;i<n-1;i++)
		{
			bool exchange=false;//本趟排序有没有元素交换
			for(int j=n-1;j>i;j--)//从后往前扫,循环n-i-1次
				if(R[j].key<R[j-1].key)
				{	swap(Rj],R[j-1]);
					exchange=true;//有元素交换
				}
			if(!exchange)//本趟没有交换就可以终止了
			return;
		}
}

2 快速排序(所有排序中平均性能最好)

  • 时间复杂度O(nlog2n);最坏情况时间复杂度O(n^2)
  • 空间复杂度O(log2n);最坏空间复杂度O(n);非就地排序!
  • 不稳定(相等的元素排序前后相对位置发生变化)
  • 每一趟划分将基准(中轴)归位
//一趟划分
int partition(RecType R[],int s,int t)//对R[s..t]进行划分
{
	int i=s,j=t;//i指头,j指尾
	RecType tmp=R[i];//基准(中轴)放到tmp里
	while(i<j)
	{	
		while(j>i&&R[j].key>=tmp.key)
			j--;//从后面开始扫如果比基准大就j--一直找到比基准小的一个R[j]
		R[i]=R[j];//把这个比基准小的R[j]移到前面

		while(i<j&&R[i].key<=tmp.key)
			i++;//从前面开始扫如果比基准小就i++一直找到比基准大的一个R[i]
		R[j]=R[i];//把这个比基准大的R[i]移到后面
	}
	R[i]=tmp;
	return i;
}

void QuikSort(RecType R[],int s,int t)//对R[s..t]进行排序
{	int i;//中轴位置
	if(s<t)
	{	i=partition(R,s,t);
		QuikSort(R,s,i-1);//中轴左边递归调用
		QuikSort(R,s,i+1);//中轴右边递归调用
	}
}

对(10,18,4,3,6,12,1,9,18,8)做一趟快速排序:
基准为tmp=R[0]=10,partition:

R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 i j
10 18 4 3 6 12 1 9 18 8 0 9 i<j
8 18 4 3 6 12 1 9 18 18 1 9 i<j
8 9 4 3 6 12 1 12 18 18 5 7 i<j
8 9 4 3 6 1 1 12 18 18 6 6
R[i]=tmp 8 9 4 3 6 1 10 12 18 18 6 6

返回i=6

二 插入排序

每趟排序不产生全局有序区,就地排。

1 直接插入排序&折半插入排序

  • 平均时间复杂度O(n^2)
  • 空间复杂度O(1)
  • 稳定
  • 每趟不产生全局有序区(不一定归位)

每趟排序将无序区的第一个元素插入到有序区(增量法)

void InsertSort(RecType R[],int n)//对R[0..n-1]递增排序
{	int i;//i表示第i趟排序; 排了几趟有序区就有几个元素
	int j;//j用来扫描有序区(R[0]...R[i-1]);
	RecType tmp;//tmp里放无序区第一个元素R[i]
	for(i=1;i<n;i++)
	{	//有序区递增,最后一个元素最大;
		//无序区第一个元素如果比有序区最后一个大,直接i++进入下一轮循环;
		//无序区第一个元素比有序区最后一个小,插入有序区
		if(R[i].key<R[i-1].key)
		{	tmp=R[i];
			j=i-1;//从后往前扫有序区
			//有序区内key大于tmp.key的都后移一个位置
			do{	//首先要把R[i-1](这一轮循环的有序区最后一个元素)挪到R[i]上
				R[j+1]=R[j];
				j--;
			}while (j>=0&&R[j].key>tmp.key);//凡是比tmp.key小的都后挪一个位置
			R[j+1]=tmp;//在j+1处插入R[i]
		}
	}	
}

直接插入查找位置时是顺序比较。

将无序区开头元素R[i]插入有序区时使用折半查找方法,可减少关键字比较次数。
折半插入排序移动元素的性能没有改善。
时间复杂度O(n^2);空间复杂度O(1);稳定。

void BinInsertSort(RecType R[],int n)
{	int i,j;
	int low,high,mid;
	RecType tmp;
	for(i=1;i<n;i++)
	{	if(R[i].key<R[i-1].key)
		{	tmp=R[i];
			//折半查找插入位置
			low=0;high=i-1;
			while(low<high)
			{	mid=(low+high)/2;
				if(tmp.key<R[mid].key)
					high=mid-1;
				else
					low=mid+1;
			}//最后high=low=mid
			//集中进行元素后移
			for(j=i-1;j>=high+1;j--)
				R[j+1]-R[j];
			//插入
			R[high+1]=tmp;
		}
	}
}

2 希尔排序(Shell Sort)

  • 时间复杂度O(n^1.3)
  • 空间复杂度O(1)
  • 不稳定
  • 每一趟不产生有序区

分组插入。
增量d:R[i+n*d] n=(0,1,2…) 是一组。
对每组直接插入排序。
希尔排序是减少增量的排序方法。

void ShellSort(RecType R[],int n)
{	int i,j,d;
	RecType tmp;
	d=n/2; //增量置初值
	while(d>0)
	{	for(i=d;i<n;i++) //扫描所有组
		{	tmp=R[i];	//对相隔d位置的一组采用直接插入排序
			j=i-d;
			while(j>=0&&tmp.key<R[j].key)
			{	R[j+d]=R[j];
				j=j-d;
			}
			R[j+d]=tmp;
		}
		d=d/2;	//减小增量
	}
}

三 选择排序

每趟产生全局有序区!(很多元素找最小的几个就很合适)
就地排!

1 直接选择排序

  • 时间复杂度O(n^2)
  • 空间复杂度O(1)
  • 不稳定
  • 每趟都产生全局有序区(有元素归位)

将无序区最小的元素选出来放在无序区第一个。

void SelectSort(RecType R[],int n)
{	int i,j;
	for(i=0;i<n-1;i++)
	{	int k=i;//k存放R[i..n-1]中最小关键字的下标,初值为i
		for(j=i+1;j<n;j++)//扫描无序区所有元素
			if(R[j].key<R[j+1].key)
				k=j;
		if(k!=i)
			swap(R[i],R[k]);
	}
}

2 堆排序

  • 最好最坏时间复杂度O(nlog2n)(时间性能与初始序列无关)
  • 空间复杂度O(1)
  • 不稳定
  • 每趟都产生全局有序区(有元素归位)

是一种树形选择排序方法。完全二叉树使用顺序存储结构。堆排序将数组看成完全二叉树的顺序存储结构进而排序。

堆的定义:R[1…n]是含n个关键字的序列,满足
爸爸结点总是大于孩子结点(大根堆) 或
爸爸结点总是小于孩子结点(小根堆)。

R[1…n]从1开始是因为把每个元素当成结点,序号是指第几个结点。

对于有n个结点的完全二叉树,最后一个分支结点是第(n/2)个结点。

即对于R[1…n/2]: R[i]<=(>=)R[2i]&&R[i]<=(>=)R[2i+1]。

构建初始堆:从最后一个分支结点开始筛选堆。
筛选堆:挑出 某分支结点的子树 里 最大的元素 放在这个分支节点上。

//筛选
void sift(RecType R[],int low,int high)//从某分支结点R[low]开始筛选
{	int i=low,j=2*i;  //i指向当前的分支结点;j指向孩子里大的那个
	RecType tmp=R[i];  //tmp存放筛选入口的那个分支结点
	while(j<=high)
	{	if(j<high&&R[j].key<R[j+1].key)//右孩子较大则j指向右孩子
			j++;
		if(tmp.key<R[j].key)//tmp(筛选入口分支结点的值)和当前j指向的孩子比较
		{	R[i]=R[j];  //大孩子换到原分支结点上
			i=j;  j=2*i;  //大孩子成为新的分支结点
		}
		else break;  
	}
	R[i]=tmp;//将tmp放到i指向的地方
}
//堆排序
void HeapSort(RecType R[],int n)
{
	//初始化堆
	for(int i=n/2;i>=1;i--)
		sift(R,i,n);
	//进行n-1趟完成堆排序
	for(int i=n;i>=2;i--)
	{	swap(R[1],R[i]);//R[1]总是最大的,swap以后归位了。
		sift(R,1,i-1);//对swap上来的新根结点进行重新筛选
		//小根堆的话从小到大排序直接sift(R,2,i)
	}
}

对(80,70,33,65,24,56,48)初始化堆(小根堆):
黄色为tmp(筛选入口那个分支结点),从n/2开始

1 ·2· ‘3’ ·4· ·5· ‘6’ ‘7’
80 70 33 65 24 56 48 i=3,j=7
80 70 33 65 24 56 48 i=2,j=5; i=5,j=10;
80 24 33 65 70 56 48 i=1,j=2; i=2,j=4; i=4,j=8;
24 65 33 80 70 56 48 结果

四 归并排序

  • 对长度为n的排序表,二路归并需要进行(log2n)趟,每趟时间为O(n)。所以最好最坏时间复杂度都为O(nlog2n)。
  • 空间复杂度O(n);非就地排序!
  • 稳定
  • 每趟产生局部有序区 不一定归位

将两个有序表合并成新有序表:两个有序表在一个数组中相邻放置,合并到局部暂存数组R1,最后将R1复制回R。

//表1:R[low..mid] 表2:R[mid+1..high]
void Merge(RecType R[],int low,int mid, int high)
{	RecType *R1;
	int i=low,j=mid+1;
	int k=0; //k是R1的下标
	R1=(RecType*)malloc((high-low+1)*sizeof(RecType));
	//二路归并部分
	while(i<=mid&&j<=high)
		if(R[i].key<=R[j].key)
			{R1[k]=R[i];i++;k++;}
		else
			{R1[k]=R[j];j++;k++;}
	//表1或表2有剩的
	while(i<=mid)
		{R1[k]=R[i];i++;k++}
	while(j<=high)
		{R1[k]=R[j];j++;k++}
	//R1复制回R
	for(k=0,i=low;i<=high;k++,i++)
		R[i]=R1[k];
	free(R1);
}
二路归并排序:

从length=1开始二路归并,每次length增大2倍,一共进行(log2n)趟

void MergeSort(RecType R[],int n)
{	int length;
	for(length=1;length<n;length=length*2)
		MergePass(R,length,n);
}
//length为每个有序表的长度,n为子表个数(不是下标)
void MergePass(RecType R[],int length,int n)
{	int i;
	//两个两个归并
	for(i=0;i+2*length-1<n;i=i+2*length) 
		Merge(R,i,i+length-1,i+2*length-1);
	//只有偶数个子表时才可能满足下面的if条件
	//有奇数个子表的话不满足if条件,最后那个子表不处理
	if(i+length-1<n-1)
		Merge(R,i,i+length-1,n-1);
}

总结一下

在这里插入图片描述
参考:https://github.com/francistao/LearningNotes/blob/master/Part3/Algorithm/Sort/%E9%9D%A2%E8%AF%95%E4%B8%AD%E7%9A%84%2010%20%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93.md

发布了36 篇原创文章 · 获赞 0 · 访问量 1774

猜你喜欢

转载自blog.csdn.net/Oneiro_qinyue/article/details/103030174