一、基本概念:
排序 : 是将n个 记录按关键字有序排列的操作
记录:将数据元素称为记录,输入集合、输出集合也是一个记录集合,可以将排序看成是线性表的一种操作。
稳定性:如果有两个数A、B关键字一样,未排序之前,A在B的前面,排序之后A依然在B的前面,那么这种排序算法就是稳定的,反之,如果B跑到A的前面去了,那么就是说这种方法是不稳定的。(跳跃式交换,都是不稳定的)
内排序:整个排序过程中,待排序的所有记录全被放置在内存中。
外排序:由于排序记录个数太多,不能同时放置在内存中,排序过程中需要在内外存之间多次交换数据。
内排序性能的影响:
①时间性能(衡量排序好坏的最重要的标志)
②辅助空间(除了存放 待排序序列 之外执行算法所需要的其它存储空间)
③算法的复杂度(算法本身的负杂度)
● 效率高的内排序算法应该是具有尽可能少的关键字比较次数,和尽可能少的记录移动次数
内排序分为: 交换排序 插入排序 选择排序 归并排序
简单算法: 冒泡排序 简单选择排序 直接插入排序
改进算法: 希尔排序 堆排序 归并排序 快速排序
二、算法描述和实现
(1)冒泡排序
基本思想:两两比较相邻记录的关键字,如果反序则交换,直到没有反序记录为止。(两两比较,大的给前走,小的给后走)
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
缺点:1 2 3 4 5 (完全有序的情况下)时间复杂度还是On^2 可以通过加状态变量来优化
越有序越快:最优为O(n),
//两两比较大的给后走,小的给前走
void BubbleSort(int *arr,int n)
{
int i,j,temp;
bool flag = true;
for(i=0;i<n-1 && flag;i++)//五个数字进行四趟冒泡就有序了。
{
flag = false;//如果进行了一趟发现没有交换的话,说明已经有序了
for(j=0;j<n-1-i;j++)//一趟之后,最后的数字最大,不再参与
{
if(arr[j]>arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
}
}
(2)快速排序 (冒泡排序的升级)越有序效率越差 ,会退化成选择排序
算法思想:通过一趟排序,将待排序记录分割成独立的两部分,其中一部分记录的关键字,均比另一部分
的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
时间复杂度:O(nlogn)
空间复杂度:O(logn)(因为递归消耗大)
是一种不稳定的排序方法,适合于大数据,不适合小数组,小数组适合于用直接插入排序。
递归实现:一次划分时间复杂度O(n)空间复杂度O(1),递归时间复杂度O(logn),空间复杂度O(logn):
基准的选取:三者取中,
递归 考试的时候最好不用
递归实现
//递归实现
//快速排序,基准左边的都比基准小,右边的都比基准大,并且,基准放在它最后该放的位置
//如果左边只有一个数字,就不用再排
int Pratition(int *arr,int low,int high)
{
int tmp = arr[low];//基准
while(low<high)
{
while((low<high) && arr[high]>=tmp)//从后往前找比基准小的
{
high--;
}
if(low == high)
{
break;
}
else
{
arr[low] =arr[high];//比基准小的交换到基准的前面
}
while((low<high)&&(arr[low]<=tmp))//从前往后找比基准大的
{
low++;
}
if(low==high)
{
break;
}
else
{
arr[high] = arr[low];//比基准大的,交换到基准的后面
}
}
arr[low] = tmp;//把基准放在中间
return low;
}
static void Quick(int *arr,int start,int end)
{
int par = Pratition(arr,start,end);
if(par>start+1)//如果前面至少还有两个数据就还得再排
{
Quick(arr,start,par-1);
}
if(par<end-1)
{
Quick(arr,par+1,end);
}
}
void QuickSort(int *arr,int n)
{
quick(arr,0,n-1);//马甲函数
}
>非递归实现
int Pratition1(int *arr,int low,int high)
{
int tmp = arr[low];
while(low<high)
{
while((low<high)&&arr[high]>=tmp)
{
high--;
}
if(low == high)
break;
if(arr[high]<tmp)
{
arr[low] = arr[high];
}
while((low<high)&& arr[low]<=tmp)
{
low++;
}
if(low == high)
break;
if(arr[low]>tmp)
{
arr[high] = arr[low];
}
}
arr[low] = tmp;
return low;
}
void QuickSort1(int *arr,int len)
{
int tmp = (int)ceil((log((double)len)/log((double)2)));//logn的推算公式 ceil 进位(上限)进位为整数 2.5-》3
int *stack = (int *)malloc(sizeof(int)*2*tmp);//申请一块内存 因为是一个数对,所以要乘2
assert(stack != NULL);
int top = 0;//栈顶指针,下标可取
int low = 0;
int high = len-1;
int par = Pratition1(arr,low,high);
if(par > low+1)
{
stack[top++] = low;
stack[top++] = par-1;
}
if(par< high-1)
{
stack[top++] = par+1;
stack[top++] = high;
}
while(top>0)
{
high = stack[--top];
low = stack[--top];
par = Pratition1(arr,low,high);
if(par > low+1)
{
stack[top++] = low;
stack[top++] = par-1;
}
if(par< high-1)
{
stack[top++] = par+1;
stack[top++] = high;
}
}
free(stack);
}
快排的优化
优化:三者取中,首先一定要先把最小数据交换到0号下标,
(3)直接插入排序
●直接插入排序是简单排序中性能最好的,是希尔排序的基础
越有序越快:最优为O(n)
基本思想:插入排序是将一个记录插入到已经排好序的队列中
最好的时间复杂度:O(n)
最坏时间复杂度:O(n^2)
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
void InsertSort(int *arr,int n)
{
int temp;
int i;
int j;
for(i=1;i<n;i++)//从第二个数字开始
{
//从后往前找,的时间复杂度低
temp=arr[i];
for(j=i-1;j>=0;j--)
{
//找数据的时候,顺带也挪了数据
if(arr[j]<=temp)//找到比它小的返回
break;
else
arr[j+1] = arr[j];//把值给后挪
}
arr[j+1]=temp;//插在比它小的后面
}
}
(4)希尔排序(直接插入排序的升级)
基本思想:采用跳跃式间隔分组将数组分组,每个小组内进行排序,(跳跃式间隔分组)
跳跃式分割策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
缺点:不稳定(跳跃式的交换数据排序都不稳定)
空间复杂度:O(1)
时间复杂度:【On(1.3~1.5次方)】【O(n3/2)】组数相互之间互素,没有公因子且最后一个分量必须是1
实质:分组后的直接插入排序
主要在于处理,处理好的话非常好写
void Shell(int *arr,int len,int gap)
{
int tmp;
int i;
int j;
for(i=gap;i<len;i++)
{
tmp = arr[i];
for(j=i-gap;j>=0;j-=gap)
{
if(arr[j]<=tmp)
{
break;
}
else
{
arr[j+gap] = arr[j];
}
}
arr[j+gap] = tmp;
}
}
void ShellSort(int *arr,int len)
{
int d[] ={5,3,1};
for(int i=0;i<sizeof(d)/sizeof(d[0]);i++)
{
Shell(arr,len,d[i]);
}
}
这是我在学习过程中的对以前知识的总结,总结的不是很到位,只是初学者,如果有什么错误,希望大家可以指出。