1.快速排序:不稳定排序,时间复杂度O(lg n)
void quicksort(int* a,int i,int j)//快排和其他排序不一样,j不是数组的大小N,而是N-//1,且因为快排是
//递归实现的,所以不能像冒泡一样在函数内通过j--实现。
{
assert(i>=0);
if(i>=j) return ;//主要是递归的退出条件也可作为输入范围正确与否判断
int xx=i,yy=j;
intk=a[i];
while(xx!=yy)
{
while((a[yy]>=k)&&(xx<yy))
yy--;
a[xx]=a[yy];
while((a[xx]<=k)&&(xx<yy))
xx++;
a[yy]=a[xx];
}
a[xx]=k;//绿色这几行不是通常理解的简单的找到比K右边比K小的和左边比K大的交换,可以自己举例试试,不要搞错了,需要仔细体会。
quicksort(a,i,xx-1);
quicksort(a,yy+1,j);
}
稳定性讨论:比如序列为 5 3 3 4 3 8 9 10,现在第一趟下来:中枢元素a[0]会与右边第一比它小的元素交换。就把原来元素3的顺序打乱了。所以不是稳定算法。
2.冒泡排序:稳定排序,时间复杂度:O(n^2) (最好情况O(N))
voidbubblesort(int* a,int i,int j)
{
assert(i>=0 &&i<=j)// 输入范围正确与否判断
bool hasChange=false;
j--;//不然a[y+1]会越界。
for(int x=j;x>i;x--)
{
for(inty=i;y<x;y++)
{ if(a[y]>a[y+1])
{
a[y]^=a[y+1]^=a[y]^=a[y+1];//y和y+1不可能是同一个变量,可放心这样交换
hasChange=true;
}
}
If(!hasChange)//如果没有交换的,直接退出,只有里层循环循环了一次,外层循环只执行了x=j的情况。这种情况下时间复杂度//为O(N)
return;
}
}
上面是从左到右冒泡,也可以从右到左冒:有些公司的笔试题比较恶心,如果给你一个数组,问你最少需要排几次的话,你必须两种都要试试!!
int bubblesort(int* a,int i,int j)//返回值为需要的冒泡趟数
{
bool hasChange=false;
i++;//不然a[y+1]会越界。
int cishu=0;
for(int x=i;x<j;x++)
{
hasChange=false;
for(int y=j-1;y>x;y--)
{
if(a[y]<a[y-1])
{
a[y]^=a[y-1]^=a[y]^=a[y-1];//y和y+1不可能是同一个变量,可放心这样交换
hasChange=true;
}
}
if(!hasChange)//如果没有交换的,直接退出,只有里层循环循环了一次,外层循环只执行了x=j的情况。这种情况下时间复杂度//为O(N)
return cishu;
else
cishu++;
}
}//这是从右向左冒泡的改进版,可以返回需要的冒泡趟数
稳定性讨论:所有可能的交换都发生在相邻元素之间,而如果相邻元素相等我们一般不会去交换,所以相等的元素之间的相对顺序不会改变。是稳定算法。
3.插入排序:稳定算法,时间复杂度:O(N^2)(最好情况O(N))
void insertsort(int* a,int i,int j)
{
assert(i>=0 &&i<=j)// 输入范围正确与否判断
int x,y;
for(x=i;x<j;x++)
{
for(y=x;y>0&&a[x]<a[y-1];y--)//如果本来数组就是排好序的,不需要//执行这个内部 循环,只需要执行外层循环,这种情况下时间复杂度为O(N)
{ a[y]^=a[y-1]^=a[y]^=a[y-1];
}
}
}
稳定性讨论:同上面冒泡算法。
4.shell排序(改进版插入排序):不稳定,时间复杂度:O(N^2)
void shellsort(int*a,int i,int j)
{
int k=j/2;
int x=0;
while (k>0)
{
for (int y=k;y<j;y++)
{
x=y-k;
if (a[y]<a[x])
{
a[y]^=a[x]^=a[y]^=a[x];
}
}
k/=2;
}
}
5.直接选择排序:不稳定排序,时间复杂度:O(N^2)
void selectsort(int*a,int i,int j)
{ assert(i>=0 &&i<=j)// 输入范围正确与否判断
for(intx=i;x<j;x++)
{ int minnum=x;
bool needChange=false;
for(int y=x;y<j;y++)
{ if(a[y]<a[minnum])
{ minnum=y;
needChange=true;
}
}
if(needChange)
a[x]^=a[minnum]^=a[x]^=a[minnum];
}
}
稳定性讨论:比如序列为 5 3 3 4 3 5 2 10,现在左边第一个5会和右边的2交换,就把原来元素5的顺序打乱了。所以不是稳定算法。
6.堆排序时间复杂度O(nlgn),不稳定。
3 (1)用大根堆排序的基本思想
4 ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
5 ② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,
6 由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
7 ③ 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
8 然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,
9 由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n- 2].keys≤R[n-1..n].keys,
10 同样要将R[1..n-2]调整为堆。
11 ……
12 直到无序区只有一个元素为止。
13 (2)大根堆排序算法的基本操作:
14 ① 初始化操作:将R[1..n]构造为初始堆;
15 ② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
16 注意:
17 ①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
18 ②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。
19 堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,
20 且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
*/
//生成大根堆
void HeapAdjust(int SortData[],int StartIndex, int Length)
{
while(2*StartIndex+1 < Length)
{
int MinChildrenIndex = 2*StartIndex+1 ;
if((2*StartIndex+2 < Length )&&(SortData[2*StartIndex+1]<SortData[2*StartIndex+2]))
MinChildrenIndex = 2*StartIndex+2//比较左子树和右子树,记录最大值的Index
if(SortData[StartIndex] < SortData[MinChildrenIndex])
{
//交换i与MinChildrenIndex的数据
int tmpData =SortData[StartIndex];
SortData[StartIndex] =SortData[MinChildrenIndex];
SortData[MinChildrenIndex] =tmpData;
//堆被破坏,需要重新调整
StartIndex = MinChildrenIndex ;
}
else
break;//比较左右孩子均大则堆未破坏,不再需要调整
}
return;
}
//堆排序
void HeapSortData(int SortData[], int Length)
{
int i=0;
//将Hr[0,Lenght-1]建成大根堆
for (i=Length/2-1; i>=0; i--)
{
HeapAdjust(SortData, i, Length);
}
for (i=Length-1; i>0; i--)
{
//与最后一个记录交换
int tmpData =SortData[0];
SortData[0] =SortData[i];
SortData[i] =tmpData;
//将H.r[0..i]重新调整为大根堆
HeapAdjust(SortData, 0, i);
}
return;
}
7.1归并排序,时间复杂度是Θ(nlgn)。稳定的排序:插入排序算法采取增量式(Incremental)的策略解决问题,每次添一个元素到已排序的子序列中,逐渐将整个数组排序完毕,它的时间复杂度是O(n2)。下面介绍另一种典型的排序算法--归并排序,它采取分而治之(Divide-and-Conquer)的策略,。
归并排序的步骤如下:
-
Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
-
Conquer: 对这两个子序列分别采用归并排序。
-
Combine: 将两个排序好的子序列合并成一个最终的排序序列。
在描述归并排序的步骤时又调用了归并排序本身,可见这是一个递归的过程。
#define LEN 8
int a[LEN] = { 5, 2, 4, 7, 1, 3, 2, 6 };
void merge(int start, int mid, int end)
{
int n1 = mid - start + 1;
int n2 = end - mid;
int left[n1], right[n2];
int i, j, k;
for (i = 0; i < n1; i++) /* left holds a[start..mid] */
left[i] = a[start+i];
for (j = 0; j < n2; j++) /* right holds a[mid+1..end] */
right[j] = a[mid+1+j];
i = j = 0;
k = start;
while (i < n1 && j < n2)
if (left[i] < right[j])
a[k++] = left[i++];
else
a[k++] = right[j++];
while (i < n1) /* left[] is not exhausted */
a[k++] = left[i++];
while (j < n2) /* right[] is not exhausted */
a[k++] = right[j++];
}
void sort(int start, int end)
{
int mid;
if (start < end)
{
mid = (start + end) / 2;
printf("sort (%d-%d, %d-%d) %d %d %d %d %d %d %d %d\n", start, mid, mid+1, end, a[0], a[1], a[2], a[3], a[4], [5], a[6], a[7]);
sort(start, mid);
sort(mid+1, end);
merge(start, mid, end);
printf("merge (%d-%d, %d-%d) to %d %d %d %d %d %d %d %d\n", start, mid, mid+1, end, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
}
}
7.2,归并排序的非递归算法:
设单链表结构为 struct ListNode {
int data ;
ListNode * link ;
};
下面的程序是以单链表为存储结构, 实现二路归并排序的算法, 要求链表不另外占用存储空间, 排序过程中不移动结点中的元素, 只改各链结点中的指针, 排序后r仍指示结果链表的第一个结点.在初始状态下, 所有待排序记录链接在一个以r为头指针的单链表中.例如,在算法实现时,利用了一个队列做为辅助存储, 存储各有序链表构成的归并段的链头指针.初始时, 各初始归并段为只有一个结点的有序链表.队列的数据类型为Queue, 其可直接使用的相关操作有置空队列操作:makeEmpty ( );将指针x加入到队列的队尾操作:EnQueue ( ListNode * x );退出队头元素, 其值由函数返回的操作:ListNode *DlQueue ( );判队列空否的函数, 空则返回1, 不空则返回0:int IsEmpty( ).
解决方法提示:
程序首先对待排序的单链表进行一次扫描, 将它划分为若干有序的子链表, 其表头 指针存放在一个指针队列中.当队列不空时, 从队列中退出两个有序子链表, 对它们进行二路归并, 结果链表的表头指针存放到队列中.如果队列中退出一个有序子链表后变成空队列, 则算法结束.这个有序子链表即为所求.
(1) 两路归并算法
void merge ( ListNode * ha, ListNode * hb,, ListNode *& hc )
{
ListNode *pa, *pb, *pc ;
if ( ha→data <= hb→data )
{
hc = ha; pa = ha→link; pb = hb;
}
else
{
hc = hb; pb = hb→link; pa = ha ;
}
pc = hc;
while ( pa && pb )
if (pa→data <= pb→data)
{
pc→link = pa; pc = pa;
pa = pa→link;
}
else
{
pc→link = pb; pc = pb;
pb = pb→link;
}
if ( pa )
pc→link = pa;
else
pc→link = pb;
};
(2) 归并排序主程序
void mergesort ( ListNode * r )
{
ListNode * s, t;
Queue Q ;
if ( ! r )
return;
s = r;
Q.EnQueue( r );
while ( s )
{
t=s→link;
while(t!=0&&s→data<=t→data)
{
s=t;
t=t→link;
}//划分为若干有序的子链表
if(t)
{
s→link = 0; //有序子链表结束
s = t;
Q.EnQueue(s);//
}
}
while (!Q.IsEmpty())
{
r = Q.DlQueue( );
if ( Q.IsEmpty( ) ) break;
s = Q.DlQueue( );
merge( r, s, t );
Q.EnQueue( t );
}
}