您的位置:首页 > 其它

基于复杂问题求解策略设计的排序算法

2017-12-02 22:18 309 查看
一:常见的复杂问题求解策略以及由其产生的算法和常见问题有:

递归与分治:大规模问题转换为小规模问题,最后要有一个边界。直接插入排序+归并排序+快速排序

动态规划:分阶段处理,多阶段决策。前面的阶段都对后面的阶段产生影响。

Floyd算法+冒泡排序

穷举(回溯+分支限界):枚举所有的可能,在枚举过程中,根据verify判定是否在当前状态基础上继续枚举。

水仙花 N皇后 马踏棋盘

贪心思想:最小生成树,Huffman树,选择排序

逆向思维:Hash查找 基数排序

以退为进:堆排序

二:基于递归与分治设计的直接插入排序和归并排序

1、直接插入排序

1.1思路

根据递归与分治的思想,首先第一个首记录一定有序,然后从第二个到最后一个,每次都将当前记录插入到其前面有序表中,最终使所有记录有序。

1.2代码实现

比较待插入记录和他的前一个记录,如果大于等于前一个记录,则不用移动。否则,就备份带插入记录,将他前面的元素分别后移,直到备份记录大于他前面的记录为止。最后将备份记录填入即可。但这种表述存在一些问题,就是可能会出现越界的情况,为了避免这种情况的发生,我们引入了哨所的概念,即在存记录的时候浪费一个空间,将a[0]用于备份记录。这样就可以将上面的表述改成,比较待插入记录和他的前一个记录,如果大于等于前一个记录,则不用移动。否则,将待插入记录存入哨中,只要之前的元素大于哨中的元素就后移。最后将哨中记录填入即可。这样因为不用每次都判断是否越界,比较时用的时间(不是复杂度)相对少些。因为这个操作比较简单实用,下面只给出用哨进行插入排序的代码。

void InsertSort(int* a,int N)
{
for(int i=2;i<=N;i++)
{
if(a[i]<a[i-1])//比较
{

a[0]=a[i];//移动
int j;
for(j=i-1;a[j]>a[0];j--)
{          //比较
a[j+1]=a[j];//移动
}
a[j+1]=a[0];//移动
}
}
}


1.3复杂度分析

最好情况:待排序序列关键字非递减有序(正序)

“比较”的次数:n-1;“移动”的次数 0

最坏情况:待排序序列关键字非递增有序(逆序)

“比较”的次数:(n-1)(n+2)/2;“移动”的次数 (n+4)(n-1)/2

时间复杂度O(n^2),空间复杂度O(1),稳定!!!

2、折半插入排序

2.1思路

因为插入排序有一个非常重要的操作,就是在前面有序的子序列找到第一个比带插入元素大的元素,所以我们可以用折半查找来定位,如此实现的插入排序我们便称为折半插入排序。

2.2代码实现

low=1,high=i-1,mid=(low+high)/2;与中间元素比较,如果中间元素大,则high=mid-1,fo否则,low=mid+1。重复上述操作,当low第一次大于high时,high右侧是第一个比带插入元素严格大的元素,插入元素为high+1(也就是low)。

void midInsertSort(int *a,int N)
{
for(int i=2;i<=N;i++)
{
a[0]=a[i];
int low=1,high=i-1;
while(low<=high)
{
int mid=(low+high)/2;
if(a[mid]>a[0]) high=mid-1;
else
low=mid+1;
}
for(int j=i-1;j>high;j--)
{
a[j+1]=a[j];
}
a[low]=a[0];
}
}


2.3复杂度分析

时间复杂度:O(n^2);空间复杂度:O(1);稳定!

3、希尔排序

3.1思路

首先根据希尔增量,将记录序列划分为d个子序列,分别对各个子序列进行直接插入排序,最后一趟d=1时对全部记录进行直接插入排序。又称缩小增量排序。(注:虽然这种排序算法也体现了以退为进的思想,但由于他调用了多次插入排序,故将他一并放入了这个模块里面)这个算法设计是由于充分洞察了直接插入排序的优点,即序列基本有序的时候直接插入排序很快,对更短的子序列进行排序是,插入排序也很快。这种算法就是将直接插入排序的优点发挥到最大,有效提升了排序的速度。

3.2代码实现

根据上面的思路跟容易给出希尔排序的代码,其中最主要的就是希尔增量的确定。使用不同的增量对希尔排序的时间复杂度的改进将不一样,甚至一点小的改变都将引起算法性能剧烈的改变。现在好像也没有确定的希尔增量选取的方法。可以看下下面链接所提供的论文:希尔排序最佳增量序列研究

https://wenku.baidu.com/view/0ffd354bcf84b9d528ea7ae3.html

下面的代码是根据k=N/2的增量序列给出的代码:

int* getDldt(int N,int* dldk,int & k)
{
k=0;
while(N!=0)
{
dldk[k++]=N/2;
N/=2;
}
return dldk;
}
void shellInsert(int* a,int dk,int N)
{
for(int i=dk+1;i<=N;i++)
{
if(a[i]<a[i-dk])
{
a[0]=a[i];
int j;
for(j=i-dk;j>0&&(a[0]<a[j]);j-=dk)
a[j+dk] = a[j];  //记录后移一增量
a[j+dk] = a[0];
}
}
}
void shellSort(int *a,int *dldk,int k,
4000
int N)
{
for(int i=0;i<k;i++)
shellInsert(a,dldk[i],N);
}


3.3复杂度分析

子序列排好序后大序列基本有序,此时直接插入插入排序效率高。此外,初始是对更短的子序列排序,插入排序的效率也很高。时间是d序列的函数,而d的确定尚待研究,最坏时间复杂度为O(n^2),实验最快会达到O(n^1.3)。是一个不稳定的排序算法。比如:11 23 12 9 18 16 25 36 30 47 30 第二趟希尔排序,增量设为3时后面的30会跑到前一个30的钱前面。

4、其他插入排序

2-路插入排序

表插入排序

呃…我也不会,只是看到鲁大师课件中给出了,就先列这里以后有时间再学习吧。

5、归并排序

5.1思路

递归与分治:递归边界:长度为一的自然有序,一分为二,分别排序,然后归并。

5.2代码实现

5.2.1用递归的思想进行实现,只要理清递归关系,写出一个归并函数进行递归即可。

void Merge(int R[],int low,int mid,int high,int T[])
{
int P_Left=low,P_Right=mid+1,P_Result=low;
while(P_Left<=mid&&P_Right<=high)
{
if(R[P_Left]<=R[P_Right])
{
T[P_Result]=R[P_Left];
P_Left++;P_Result++;
}
else
{
T[P_Result]=R[P_Right];
P_Right++;P_Result++;
}
}
while(P_Left<=mid)
{
T[P_Result]=R[P_Left];
P_Left++;P_Result++;
}
while(P_Right<=high)
{
T[P_Result]=R[P_Right];
P_Right++;P_Result++;
}
for(int i=low;i<P_Result;i++)
{
R[i]=T[i];
}//将中间数组的值赋到原数组的对应位置
}//将两个有序的序列归并一个有序的序列,跟求并集一样。
void MSort(int R[],int low,int high,int Temp[])
{
if(low<high){
int mid=(low+high)/2;
MSort(R,low,mid,Temp); //递归公式
MSort(R,mid+1,high,Temp);//递归公式
Merge(R,low,mid,high,Temp);
}
}


5.2.2非递归实现

因为单个元素一定有序,不用进行划分,直接两两进行归并,直到最后只有两个归并块为止。

5.3复杂度分析

时间复杂度:O(NlogN);空间复杂度:O(N);稳定!

6、快速排序

6.1思路

快速排序也是基于分治思想提出的,找一个“杠杆”,杠杆左边都比“杠杆”元素”小”,右边都比“杠杆”元素“大”,

然后对“杠杆”左边和右边分别进行递归即可。

6.2代码实现

有最左侧元素做“枢轴”,存入“哨所”。(枢轴元素的选择会影响算法的速度)设low和high指向两端,high向左移动,一旦遇到小于枢纽的元素,则将其移动到左侧,放入low指向的位置;low向右移动,一旦遇到大于枢轴的元素,则将其移动到右侧,放入high指向的位置;high和low如此移动下去,直到high==low,记录下枢轴的位置,此时杠杆左边都比“杠杆”元素”小”,右边都比“杠杆”元素“大”。

int Partition(int *a,int low,int high)
{
a[0]=a[low];
while(low<high)
{
while(high>low&&a[high]>=a[0])//防止在找的过程中high==low
high--;
a[low]=a[high];
while(low<high&&a[low]<=a[0])
low++;
a[high]=a[low];
}
a[low]=a[0];
return low;
}
void Qsort(int *a,int low,int high)
{
if(low<high)
{
int Partition_=Partition(a,low,high);
Qsort(a,low,Partition_-1);
Qsort(a,Partition_+1,high);
}
}


6.3复杂度分析

平均复杂度:O(NlogN) 平均最快(因为不要脸,不跟基数排序玩….23333)

最坏时间复杂度:O(N^2);

平均空间复杂度:O(logN);

最坏空间复杂度:O(N);

改进:枢轴在两侧与中间中择一。

不稳定!!!

三:基于贪心思想设计的选择排序算法

1、简单选择排序

1.1思路

每一趟选择出当前最小的交换到最前面,至多N-1趟排序完成。

1.2代码实现

void selectSort(int *a,int N)

{

for(int i=1;i<=N-1;i++)

{

int minn=i;

for(int j=i+1;j<=N;j++)

{

if(a[minn]>a[j])

minn=j;

}

if(minn!=i)

{

int temp=a[minn];

a[minn]=a[i];

a[i]=temp;

}

}

}

1.3复杂度分析

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

空间复杂度O(1);

不稳定!!!

1.4不稳定的原因分析

举个栗子,3、3*、3**、2。将这一组数从小到大排列,三后面的* 用来标记三出现的先后顺序。按照上面给出的选择排序代码,第一次找最小的数的时候,将2跟3交换,原序列中第一个3在排完序之后成了最后一个。所以是不稳定,但这种不稳定可以改进,下面便给出再时间复杂度和空间复杂度都不变的情况下,一个稳定的选择排序算法。

2、简单排序算法的稳定性改进

2.1思路

再找到最小元素的下标minn是,不直接将他与a[i]交换,而是在i+1与minn之间找与a[i]相等的元素,找到就让a[i]和找到的元素交换。这样,最多讲过n-i次交换就可以保证其a[i]成为离a[minn]最近并且与无序块首记录关键字相等的元素,此时再将a[i]和a[minn]交换,则排序算法保持稳定性。

2.2代码实现

void selectSort(int *a,int N)
{
for(int i=1;i<=N-1;i++)
{
int minn=i;
for(int j=i+1;j<=N;j++)
{
if(a[minn]>a[j])
minn=j;
}
if(minn!=i)
{
for(int k=i+1;k<minn;k++)
{
if(a[k]==a[i])
{
int temp=a[k];
a[k]=a[i];
a[i]=temp;
}
}
int temp=a[minn];
a[minn]=a[i];
a[i]=temp;
}
}
}


2.3复杂度分析

比简单排序稍慢,但从复杂度分析的角度,其时间复杂度并没有提高,都是O(n^2)。其空间复杂度也是O(1),也没有发生改变。

2.4稳定性分析

同样举个例子,3、3-、3–、2。将这一组数从小到大排列,三后面的- 用来标记三出现的先后顺序。按照上面的排序中算法,在与无序块首记录关键字相等的元素交换的过程中,第一次得到的序列是 3-、3、3–、2,第二次得到的序列是,3–、3、3- 、2。最后再将2和3–交换,得到的就是2、3、3-、3–这个序列,可以看到,按照改进的排序算法并没有改变原序列中想等元素的顺序,所以改进的选择排序算法是稳定的!!!

具体参考论文http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=CJFQ&dbname=CJFDLAST2016&filename=RJDK201602023&uid=WEEvREcwSlJHSldRa1FhcTdWZDhML2VlZDlxK0FHL2ZORXFRR1pZL3ZxND0=

四:基于动态规划设计的冒泡排序算法

1、动态规划的思想

问题分解:全局问题分解为局部问题。

迭代求解:由初始阶段逐步递推,前一阶段影响后一阶段知道最终得到全局解。

2、冒泡排序

2.1思路

每一趟排序,每一躺排序时,从首记录开始相邻两数进行比较,逆序则交换,每趟排序都会使当前“最大”的数“沉到末尾”,小数则逐步“上升”,重复n-1趟排序完成。怎么改进?这样会出现重复操作的情况,即在中间的某一趟已经排序完成了,但他仍会执行后面无意义的操作。那么怎么判断他已经排序完成了呢,即在中间的某趟没有发生交换既证明排序已经结束。

2.2代码实现

void bubbleSort(int *a,int N)
{
for(int i=1;i<N;i++)
{
int flag=0;
for(int j=1;j<=N-i;j++)
{
int temp;
if(a[j+1]<a[j])
{
flag=1;
temp=a[j+1];
a[j+1]=a[j];
a[j]=temp;
}
}
if(flag==0)
break;
}
}


2.3复杂度分析

最好的情况:序列开始就有序的情况。时间复杂度O(n);

最坏的情况:序列逆序的情况。时间复杂度O(n^2);

这两种情况的空间复杂度都为O(1);

稳定!

五:基于逆向思维设计的基数排序算法

1、思路

所有排序都必须要有的一个原子操作就是就是比较,那如何不比较也能排序呢?这种不比较也能排序的算法就是基数排序,基数排序的提出是循序渐进的,他是先解决了一个最简单的问题,即关键字不重复,取值范围有限,即分配排序。然后解决了一个稍微复杂的问题即,关键字可重复,取值范围有限,即桶排序。最后便是关键字可重复,取值范围大,这里采用的解决办法是多趟分配与收集,每一趟范围小。在我看来,这里也体现了动态规划的思想。

2、代码实现

从个位数开始分别进行分配和收集,每趟的分配都存到队列数组中,然后顺序收集。

下面的代码是对int型正数进行排序,如果有负数要单独处理。

for(int i=1;i<=10;i++)
{
Distribute(a,S,i,N);
collect(a,S);
}
void Distribute(int *a,LinkQueue* S,int k,int N)
{
int temp;
for(int i=0;i<N;i++)
{    temp=a[i];
for(int j=1;j<k;j++)
{
temp=temp/10;
}
temp%=10;
EnQueue(S[temp],a[i]);
}
}
void collect(int *a,LinkQueue* S)
{
int j=0;
for(int i=0;i<10;i++)
{
while(S[i].front_!=S[i].rear_)
DeQueue(S[i],a[j++]);
}
}


3、复杂度分析

时间复杂度:O(n+radix) ;

空间复杂度 : O(n+radix);

稳定!!!漂亮啊!!!

六:基于以(zhen)退(long)为(qi)进(ju)思想设计的堆排序

1、堆排序的引入

第一趟选择极值元素时,能否多作些工作,为后面选择极值元素带来便利呢。

2、堆排序

2.1思路

小顶堆对应的完全二叉树中任意结点均比其孩子小或者相等;大顶堆对应的完全二叉树中任意结点均比其孩子大或相等。

小顶堆堆顶(首元素)最小,大顶堆堆顶最大,子树也是堆。

基本思想:利用堆每次选择出最大的交换到末尾。

具体方法:

(1)先建初始大顶堆;

(2)堆顶与堆尾互换(最大记录换到最后);忽略堆尾,将前N-1个记录组成的树重新调整成大顶
9f7c
堆;

(3)重复上一步N-1趟即可

2.2代码实现

Floyd在实现堆排序的过程中也运用到了以退为进的思想,先假设得到了初始大顶堆,即先解决了第二步筛选后,运用第二问的结论调整建堆建立初始大顶堆。

2.2.1筛选的实现

备份堆顶,较堆顶与其两个孩子,若堆顶不是最大的则将两个孩子中大的“上移”,之后的子树可能不再是堆,重复上述操作至最后(当前堆顶大于等于孩子或无孩子)

2.2.2建立初始大顶堆

从最后一个非叶子结点向根结点方向逐步调整建堆。

void HeapSort(int *a,int N)
{
for(int i=N/2;i>0;i--) //第一个非叶子结点是N/2
HeapAdjust(a,i,N);
for(int i=N;i>1;i--)
{
int temp=a[1];
a[1]=a[i];
a[i]=temp;
HeapAdjust(a,1,i-1);
}

}
void HeapAdjust(int *a,int k,int N)
{
a[0]=a[k];
for(int i=2*k;i<=N;i*=2)//i*2变成i对应的左孩子
{
if(i<N&&a[i]<a[i+1])//避免i==N的情况
i++;//i始终指向指向大孩子
if(a[0]>=a[i]) break;
else
{
a[k]=a[i];
k=i;
}

}
a[k]=a[0];
}


2.3复杂度分析

最坏时间复杂度:O(nlogn);

空间复杂度:O(1);

不稳定!!!

———————————————————–分界线——————————————————–

鲁大师好多课件都有的一句话:狡黠者鄙读书,无知者羡读书,惟明智之士用读书,然书并不以用处告人,用书之智不在书中,而在书外,全凭观察得之。共勉!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  排序算法
相关文章推荐