1、冒泡排序
void BubbleSort(Sqlist &L)
{
bool flag=0;//这里设置一个标志位,用来判断是否进行了交换,如果没有交换,说明不需要再排,这个就是冒泡排序的好处
for(int i=1;i<L.length;i++)//只需要排L.length-1次
{
for(int j=L.length-1;j>=1;j--)//每一次都要从后往前找
{
if(L.r[j-1]>L.r[j])
{
swap(j-1,j,L);
flag=1;
}
}
if(!flag)
break;
}
}
思想:从后面两两比较,若是小的,就进行交换,这样每循环一次就冒泡出一个最小。同时其它的数进行了移位。
如果排序较好,那么需要移动的次数还是比较少的。
2、选择排序
#include "stdafx.h"
#include<iostream>
using namespace std;
const int maxsize=5;
struct Sqlist
{
int r[maxsize];
int length;
Sqlist():length(0){}
};
void swap(int i,int j,Sqlist &L)
{
int temp=L.r[i];
L.r[i]=L.r[j];
L.r[j]=temp;
}
void SlectSort(Sqlist &L)
{
for(int i=0;i<L.length;i++)//
{
int min=i;//这种游标法非常好,我很喜欢
for(int j=i+1;j<=L.length;j++)
{
if(L.r[j]<L.r[min])
min=j;
}
if(i!=min)
swap(i,min,L);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
Sqlist L;
for(int i=0;i<maxsize;i++)
{
cin>>L.r[i];//以后就用这种方法读入数据了
L.length++;
}
SlectSort(L);
for(int i=0;i<L.length;i++)
cout<<L.r[i];//输出输入不加endl换行很方便</p><p>
return 0;
}
思想:相当于是不断从后面找到最小的,放到前面相应的位置
3、直接插入排序
void InsertSort(Sqlist &L)
{
for(int i=1;i<L.length;i++)//从第二个位置开始
{
int temp=L.r[i];
int j;
if(L.r[i]<L.r[i-1])
{ for(j=i-1;L.r[j]>L.r[i];j--)//注意是停止条件,为>
L.r[j+1]=L.r[j];
L.r[j+1]=temp; //最后再插过去 这样节省时间,前面只要做覆盖移动即可
}
}
}
思想:向前插到合适的位置。
4、希尔排序
void shellsort(Sqlist &L)
{
int i,j;
int increment=L.length;
do
{
increment=increment/3+1;
for(i=increment+1;i<L.length;i++)
{
if(L.r[i]<L.r[i-increment])
{
int temp=L.r[i];//接下来和插入排序有点像了
for(j=i-increment;L.r[j]>L.r[i];j-=increment)
L.r[j+increment]=L.r[j];
L.r[j+increment]=temp;
}
}
}
while(increment>1); //这个do while记住是有;的
}
思想:希尔排序是插入排序的改进版,只是更改了插入间隔为increment
(1)采用do while,从increment +1 遍历
(2)和前一个间隔值i-incremtnt比较,若小,则进入循环 (条件为,间隔值比i值大,)若是,则进行用前值 j 覆盖后值j+increment,最终用i值来刷新前值j+increment
(3)while 后面记得加上;
5、堆排序
void HeapAdjust(Sqlist &L,int s,int m)
{
int temp,j;
temp=L.r[s];
for(j=2*s;j<=m;j*=2)//定位要调整的小堆的位置,
{
if(j<m && L.r[j]<L.r[j+1])//和被调整的数对比,看是哪一个分支,然后用分支覆盖掉,并滑向分支,最后插入调整数
j++;
if(temp>=L.r[j])
break;
L.r[s]=L.r[j];
s=j;
}
L.r[s]=temp;
}
void HeapSort(Sqlist &L)
{
L.length--;//从1开始排,若length为5则变成4,刚好是末端数
int i;
for(i=L.length/2;i>0;i--) //从第一个有子节点的位置开始调整为大顶堆,直到i=1
HeapAdjust(L,i,L.length);
for(i=L.length;i>1;i--)//不断交换,共交换L.length-1次
{
swap(i,1,L);//交换
HeapAdjust(L,1,i-1);//调整大顶堆,由于其它的已经调整好,只有第一个没有调整,因此代入下去即可
}
L.length++;//恢复 这样size还是没有变,只是r[0]没有用到,这里主要是方便理解
}
堆排序的思想:
(1)全部调整堆,使其满足大顶堆的要求,这样堆顶的元素是最大的。(调整顺序:从length/2开始调整,刚好是最底下一个子堆)
(2) 交换和调整大顶堆n-1次,就完成了整个排序。
调整大顶堆的思想:
(1)存储要调整点的值
(2)找到子堆中,最大点的位置,
(3)如果最大点比调整点大,用最大点覆盖掉调整点,
(4)处理最大点所在的子堆,在里面找到调整点该放到哪个位置
(5)重复步骤(3)
(5)完毕后,将调整点插入到合适的位置。
6、归并排序
6.1 归并排序的递归实现
这个是自己根据大话数据结构改进的,跟以前排序方法,风格统一
void merge(Sqlist &L,int i,int m,int n)
{
int j,k,l;
int ti=i;
int tsize=n-i+1;
int *S=new int[n-i+1];//初始化数组,用来存储排序,做缓存用
for(int t=0;t<tsize;t++)
S[t]=0;
for(j=m+1,k=0;i<=m && j<=n;k++)
{
if(L.r[i]<L.r[j])//看大哪边大就哪哪边的,这种i++的方式非常好
S[k]=L.r[i++];
else
S[k]=L.r[j++];
}
if(i<=m)//若j那边取完了i这边还有剩的,那么就把剩下的直接加在后面
{
for(l=0;l<=m-i;l++)
S[k+l]=L.r[i+l];
}
if(j<=n)
{
for(l=0;l<=n-j;l++)
S[k+l]=L.r[j+l];
}
for(int a=0;a<tsize;a++)
L.r[ti+a]=S[a];
delete S;//不要忘了释放动态数组
}
void msort(Sqlist &L,int s,int t)//s为待排序数组的第一个元素下标,t为待排序数组最后一个元素的下标
{
int m;
if(s==t)
return;//直接返回,无需归并
else if(s+1==t)
{
if(L.r[s]>L.r[t])//只要交换两个元素的位置,就已经做好了排序,不需要在做归并
swap(s,t,L);
return;
}
else
{
m=(s+t)/2;
msort(L,s,m);
msort(L,m+1,t);
merge(L,s,m,t);
}
}
void MergeSort(Sqlist &L)
{
msort(L,0,L.length-1);
}
排序思想:
(1)要想将一个数组排序,那么就是从中间分开,将两边排好序之后,再归并,就完成排序。
(2)而两边要想排好序,也是要做同样的工作。
(3)因此用到递归
(4)递归的工作是,分成两边,两边排好序(递归),归并(return)
(5)递归的出口是,当数组中元素个数为1时,说明已经排好序,且无需做分开,归并的工作,就能返回(return)。当元素个数为2的时候,也无需分开,只需要简单排序就能返回(return)。
归并思想:
(1)对于一个数组,其中i...m已经排好序,m+1...n已经排好序,将其归并i...n要做的工作是。
(2)定义一个i到n长度的动态数组,不断比较两边的大小,哪边大,就将哪边的值放到动态数组中,同时那边的游标往前走一个。
(3)当一边已经完成,而另外一边还没有放完时,就将剩下的接在数组后面。
(4)将原来数组中i...n部分的值更新为动态数组中的值。
6.2 归并排序的非递归实现
7、快速排序
int partition(Sqlist &L,int low,int high)//简单来讲
{
int pivotkey;
pivotkey=L.r[low];
while(low<high)
{
while(low<high && L.r[high]>=pivotkey)
high--;
swap(low,high,L);
while(low<high && L.r[low]<=pivotkey)
low++;
swap(low,high,L);
}
return low;
}
void qsort(Sqlist &L,int low,int high)
{
int pivot;
if(low<high)
{
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);
}
快速排序的思想是:
(1)要想将一个数组排序,可以将其分成两部分,较大值部分可较小值部分,再将两部分做同样的工作后,就完成了排序。
(2)因此还是用到递归
(3)递归的工作是,分成两边,左边为小值部分,右边为大值部分。
(3)递归出口,当小数组只有一个元素的时候直接返回(return),当有两个元素的时候,交换排序后返回(return)
分部实现:
(1)选取一个关键字,为第一个值,排序结果是,让一部分比它小,一部分比它大。
(2)不另外建立缓存
(3)采用左右移动的方式完成,先比较高端部分,高端有值比关键字小,那么将高端值交换到低端来,
(4)再比较低端,若低端有值比关键字大,那么将其交换到高端。
(5)如此往复,就完成了分部工作。
这种交换方法,省空间,而且特别巧妙。
优化方法:
(1)优化选取关键字
(2)优化不必要的交换,相当于是备份了一个,temp然后用覆盖的方式 ,就和堆排序里面有点像。
(3)优化递归操作
总结
可以将算法非为两类:
简单算法:冒泡、简单选择、直接插入
改进算法:希尔、堆、归并、快排
总的来说,改进算法中堆和归并排序各种情况算法时间复杂度都是nlogn。
而希尔和快排最坏情况是n2,快排最好是nlogn,希尔最坏是n1.5这里看取的增量而定。
递归需要占用logn的空间,而归并排序同样需要一个数组来存储。耗费n的空间。
冒泡和直接插入最好情况时间复杂度为n,是排序很好的情况。
从平均情况来看,后面三种算法要比希尔排序好,但是比前面三种简单算法好
从最好情况来看,冒泡排序和直接插入排序又更胜一筹,如果排序基本有序,那么就不要考虑复杂算法。
从最坏情况来看,堆排序与归并排序又强过快排以及其它排序
可以看出:堆排序与归并排序,表现的比较稳定
但是快排:有时候表现的特别好,有时候比较差
空间复杂度来讲:归并排序和快速排序都是用的递归,所以对空间的要求比较大,反而堆排序堆空间要求比较低。
稳定性来看:归并排序最好
从排序个数来看:当个数较少时采用简单排序法
总之,经过优化的快速排序是性能最好的排序方法,但是要根据不同的场合来考虑不同的算法。