希尔排序是将组分段,进行插入排序.对想提高C#语言编程能力的朋友,我们可以互相探讨一下。
如:下面的程序,并没有实现多态,来,帮它实现一下。
public class ShellSorter
{
public void Sort(int[] list)
{
int inc;
for (inc = 1; inc <= list.Length / 9; inc = 3 * inc + 1) ;
for (; inc > 0; inc /= 3)
{
for (int i = inc + 1; i <= list.Length; i += inc)
{
int t = list[i - 1];
int j = i;
while ((j > inc) && (list[j - inc - 1] > t))
{
list[j - 1] = list[j - inc - 1];
j -= inc;
}
list[j - 1] = t;
}
}
}
}
public class MainClass
{
public static void Main()
{
int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
ShellSorter sh = new ShellSorter();
sh.Sort(iArrary);
for (int m = 0; m <= 13; m++)
Console.WriteLine("{0}", iArrary[m]);
}
}
View Code
插入排序
public class InsertionSorter
{
public void Sort(int[] list)
{
for (int i = 1; i < list.Length; ++i)
{
int t = list[i];
int j = i;
while ((j > 0) && (list[j - 1] > t))
{
list[j] = list[j - 1];
--j;
}
list[j] = t;
}
}
}
public class MainClass
{
public static void Main()
{
int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
InsertionSorter ii = new InsertionSorter();
ii.Sort(iArrary);
for (int m = 0; m <= 13; m++)
Console.WriteLine("{0}", iArrary[m]);
}
}
View Code
选择排序
public class SelectionSorter
{
private int min;
public void Sort(int[] list)
{
for (int i = 0; i < list.Length - 1; ++i)
{
min = i;
for (int j = i + 1; j < list.Length; ++j)
{
if (list[j] < list[min])
min = j;
}
int t = list[min];
list[min] = list[i];
list[i] = t;
// Console.WriteLine("{0}",list[i]);
}
}
}
public class MainClass
{
public static void Main()
{
int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
SelectionSorter ss = new SelectionSorter();
ss.Sort(iArrary);
for (int m = 0; m <= 13; m++)
Console.WriteLine("{0}", iArrary[m]);
}
}
View Code
============================================== 常见排序算法介绍
1、稳定排序和非稳定排序
简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的。反之,就是非稳定的。
比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4
的前面。假如变成a1,a4,a2,a3,a5就不是稳定的了。
2、内排序和外排序
在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺
序排序方法称为外排序。
3、算法的时间复杂度和空间复杂度
所谓算法的时间复杂度,是指执行算法所需要的计算工作量。
一个算法的空间复杂度,一般是指执行这个算法所需要的内存空间。 ================================================================================ */
/* ================================================ 功能:选择排序 输入:数组名称(也就是数组首地址)、数组中元素个数 ================================================ */ /* ==================================================== 算法思想简单描述:
在要排序的一组数中,选出最小的一个数与第一个位置的数交换; 然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环 到倒数第二个数和最后一个数比较为止。
选择排序是不稳定的。算法复杂度O(n2)--[n的平方] ===================================================== */ void select_sort(int *x, int n) { int i, j, min, t;
for (i=0; i<n-1; i++) /*要选择的次数:0~n-2共n-1次*/ { min = i; /*假设当前下标为i的数最小,比较后再调整*/ for (j=i+1; j<n; j++)/*循环找出最小的数的下标是哪个*/ { if (*(x+j) < *(x+min)) { min = j; /*如果后面的数比前面的小,则记下它的下标*/ } }
if (min != i) /*如果min在循环中改变了,就需要交换数据*/ { t = *(x+i); *(x+i) = *(x+min); *(x+min) = t; } } }
/* ================================================ 功能:直接插入排序 输入:数组名称(也就是数组首地址)、数组中元素个数 ================================================ */ /* ==================================================== 算法思想简单描述:
在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排 好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数 也是排好顺序的。如此反复循环,直到全部排好顺序。
直接插入排序是稳定的。算法时间复杂度O(n2)--[n的平方] ===================================================== */ void insert_sort(int *x, int n) { int i, j, t;
for (i=1; i<n; i++) /*要选择的次数:1~n-1共n-1次*/ { /* 暂存下标为i的数。注意:下标从1开始,原因就是开始时 第一个数即下标为0的数,前面没有任何数,单单一个,认为 它是排好顺序的。 */ t=*(x+i); for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,这里就是下标为i的数,在它前面有序列中找插入位置。*/ { *(x+j+1) = *(x+j); /*如果满足条件就往后挪。最坏的情况就是t比下标为0的数都小,它要放在最前面,j==-1,退出循环*/ }
*(x+j+1) = t; /*找到下标为i的数的放置位置*/ } }
/* ================================================ 功能:冒泡排序 输入:数组名称(也就是数组首地址)、数组中元素个数 ================================================ */ /* ==================================================== 算法思想简单描述:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上 而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较 小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要 求相反时,就将它们互换。
下面是一种改进的冒泡算法,它记录了每一遍扫描后最后下沉数的 位置k,这样可以减少外层循环扫描的次数。
冒泡排序是稳定的。算法时间复杂度O(n2)--[n的平方] ===================================================== */
void bubble_sort(int *x, int n) { int j, k, h, t;
for (h=n-1; h>0; h=k) /*循环到没有比较范围*/ { for (j=0, k=0; j<h; j++) /*每次预置k=0,循环扫描后更新k*/ { if (*(x+j) > *(x+j+1)) /*大的放在后面,小的放到前面*/ { t = *(x+j); *(x+j) = *(x+j+1); *(x+j+1) = t; /*完成交换*/ k = j; /*保存最后下沉的位置。这样k后面的都是排序排好了的。*/ } } } }
/* ================================================ 功能:希尔排序 输入:数组名称(也就是数组首地址)、数组中元素个数 ================================================ */ /* ==================================================== 算法思想简单描述:
在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点, 并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为 增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除 多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现 了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中 记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量 对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成 一组,排序完成。
下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量, 以后每次减半,直到增量为1。
希尔排序是不稳定的。 ===================================================== */ void shell_sort(int *x, int n) { int h, j, k, t;
for (h=n/2; h>0; h=h/2) /*控制增量*/ { for (j=h; j<n; j++) /*这个实际上就是上面的直接插入排序*/ { t = *(x+j); for (k=j-h; (k>=0 && t<*(x+k)); k-=h) { *(x+k+h) = *(x+k); } *(x+k+h) = t; } } }
/* ================================================ 功能:快速排序 输入:数组名称(也就是数组首地址)、数组中起止元素的下标 ================================================ */ /* ==================================================== 算法思想简单描述:
快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟 扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次 扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只 减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧) 的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理 它左右两边的数,直到基准点的左右只有一个元素为止。它是由 C.A.R.Hoare于1962年提出的。
显然快速排序可以用递归实现,当然也可以用栈化解递归实现。下面的 函数是用递归实现的,有兴趣的朋友可以改成非递归的。
快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n2)
===================================================== */ void quick_sort(int *x, int low, int high) { int i, j, t;
if (low < high) /*要排序的元素起止下标,保证小的放在左边,大的放在右边。这里以下标为low的元素为基准点*/ { i = low; j = high; t = *(x+low); /*暂存基准点的数*/
while (i<j) /*循环扫描*/ { while (i<j && *(x+j)>t) /*在右边的只要比基准点大仍放在右边*/ { j--; /*前移一个位置*/ }
if (i<j) { *(x+i) = *(x+j); /*上面的循环退出:即出现比基准点小的数,替换基准点的数*/ i++; /*后移一个位置,并以此为基准点*/ }
while (i<j && *(x+i)<=t) /*在左边的只要小于等于基准点仍放在左边*/ { i++; /*后移一个位置*/ }
if (i<j) { *(x+j) = *(x+i); /*上面的循环退出:即出现比基准点大的数,放到右边*/ j--; /*前移一个位置*/ } }
*(x+i) = t; /*一遍扫描完后,放到适当位置*/ quick_sort(x,low,i-1); /*对基准点左边的数再执行快速排序*/ quick_sort(x,i+1,high); /*对基准点右边的数再执行快速排序*/ } }
/* ================================================ 功能:堆排序 输入:数组名称(也就是数组首地址)、数组中元素个数 ================================================ */ /* ==================================================== 算法思想简单描述:
堆排序是一种树形选择排序,是对直接选择排序的有效改进。 堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当 满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2) 时称之为堆。在这里只讨论满足前者条件的堆。
由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以 很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。 初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序, 使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点 交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点 的堆,并对它们作交换,最后得到有n个节点的有序序列。
从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素 交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数 实现排序的函数。
堆排序是不稳定的。算法时间复杂度O(nlog2n)。
*/ /* 功能:渗透建堆 输入:数组名称(也就是数组首地址)、参与建堆元素的个数、从第几个元素开始 */ void sift(int *x, int n, int s) { int t, k, j;
t = *(x+s); /*暂存开始元素*/ k = s; /*开始元素下标*/ j = 2*k + 1; /*右子树元素下标*/
while (j<n) { if (j<n-1 && *(x+j) < *(x+j+1))/*判断是否满足堆的条件:满足就继续下一轮比较,否则调整。*/ { j++; }
if (t<*(x+j)) /*调整*/ { *(x+k) = *(x+j); k = j; /*调整后,开始元素也随之调整*/ j = 2*k + 1; } else /*没有需要调整了,已经是个堆了,退出循环。*/ { break; } }
*(x+k) = t; /*开始元素放到它正确位置*/ }
/* 功能:堆排序 输入:数组名称(也就是数组首地址)、数组中元素个数 */ void heap_sort(int *x, int n) { int i, k, t; int *p;
for (i=n/2-1; i>=0; i--) { sift(x,n,i); /*初始建堆*/ }
for (k=n-1; k>=1; k--) { t = *(x+0); /*堆顶放到最后*/ *(x+0) = *(x+k); *(x+k) = t; sift(x,k,0); /*剩下的数再建堆*/ } }
void main() { #define MAX 4 int *p, i, a[MAX];
/*录入测试数据*/ p = a; printf("Input %d number for sorting :\n",MAX); for (i=0; i<MAX; i++) { scanf("%d",p++); } printf("\n");
/*测试选择排序*/
p = a; select_sort(p,MAX); /**/
/*测试直接插入排序*/
/* p = a; insert_sort(p,MAX); */
/*测试冒泡排序*/
/* p = a; insert_sort(p,MAX); */
/*测试快速排序*/
/* p = a; quick_sort(p,0,MAX-1); */
/*测试堆排序*/
/* p = a; heap_sort(p,MAX); */
for (p=a, i=0; i<MAX; i++) { printf("%d ",*p++); }
printf("\n"); system("pause"); }
============================================
排序算法
Copyright:bamboottm.my163.com //转载请保留出处
计算机处理数据包括排序、检索(查找)、修改和删除操作。我们研究排序算法有几点充分理由。首先,是因为它实际应用非常频繁,计算机厂家…… //这个你要听吗? 不废话了
。
//为了说明方便.定义如下数组: a:array[1..10] of integer;temp: 中间变量 排序: 从大到小
l 选择排序 1.基本的 选择排序 <1>基本思想
首先从要排序的数中选择最大的数,将它放在第一个位置,然后从剩下的数中选择最大的数放在第二个位置,如此继续,直到最后从剩下的两个数中选择最大的数放在倒数第二个位置,
剩下的一个数放在最后位置,完成排序.
下表是六个元素的排序的过程
4 5 7 1 2 3
┗━━┛
5 4 7 1 2 3 ┗━━━━┛
7 4 5 1 2 3 ┗━━━━━━┛
7 4 5 1 2 3
┗━━━━━━━━━━┛ 第一趟结束 ⑦ 4 5 1 2 3
┗━┛ 7 5 4 1 2 3
┗━━━┛ 7 5 4 1 2 3 ┗━━━━━┛ 7 5 4 1 2 3 ┗━━━━━━━┛ 第二趟结束 7 ⑤ 4 1 2 3 ┗━┛
7 5 4 1 2 3 ┗━━━┛
7 5 4 1 2 3
┗━━━━━┛ 第三趟结束
7 5 ④ 1 2 3
┗━┛ 7 5 4 2 1 3 第四趟结束 ┗━━━┛ 7 5 4 ③ 1 2 ┗━┛ 第五趟结束
7 5 4 3 ② ①
<2>算法实现
for i:=1 to 9 do
for j:=i+1 to 10 do
if a[i]<a[j] begin
temp:=a[i];
a[i]:=a[j];
a[j]:=temp;
end;
2.改进
以上排序方案每次交换两个元素需要执行三个语句,过多的交换必定要花费许多时间.改进方案是在内循环的比较中找出最大值元素的下标,在内循环结束时才考虑是否要调换.
代码如下
for i:=1 to 9 do
begin
k:=i;
for j:=i+1 to 20 do
if a[j]>a[k]
then k:=j;
if i<k {不可能大于}
then begin
temp:=a[i];
a[i]:=a[k];
a[k]:=temp;
end;
end;
l 冒泡排序 1.基本的冒泡排序 <1> 基本思想
依次比较相邻的两个数,把大的放前面,小的放后面.即首先比较第1个数和第2个数,大数放前,小数放后.然后比较第2个数和第3个数......直到比较最后两个数.第一趟结束,最小的
一定沉到最后.重复上过程,仍从第1个数开始,到最后第2个数.然后...... 由于在排序过程中总是大数往前,小数往后,相当气泡上升,所以叫冒泡排序.
下面是6个元素的排序的过程
4 5 7 1 2 3
┗━━┛ 5 4 7 1 2 3 ┗━━┛ 5 7 4 1 2 3 ┗━━┛ 5 7 4 1 2 3
┗━━┛ 5 7 4 2 1 3
┗━━┛ 第一趟结束
5 7 4 2 3 ① ┗━━┛
7 5 4 2 3 1 ┗━━┛
7 5 4 2 3 1 ┗━━┛ 7 5 4 2 3 1
┗━━┛ 第二趟结束 7 5 4 3 ② 1
┗━━┛ 7 5 4 3 2 1 ┗━━┛
7 5 4 3 2 1 ┗━━┛ 第三趟结束
7 5 4 ③ 2 1 ┗━━┛ 7 5 4 3 2 1 ┗━━┛ 第四趟结束
7 5 ④ 3 2 1 ┗━━┛ 第五趟结束
⑦ ⑤ 4 3 2 1
<2> 算法实现
for i:=1 to 9 do
for j:=1 to 10-i do
if a[j]<a[j+1]
then begin
temp:=a[j];
a[j]:=a[j+1];
a[j+1]:=temp;
end;
2 改进
上例中,可以发现,第二趟结束已经排好序.但是计算机此时并不知道已经排好序.所以,还需进行一次比较,如果没有发生任何数据交换,则知道已经排好序,可以不干了.因此第
三趟比较还需进行,第四趟、第五趟比较则不必要.
我们设置一个布尔变量bo 来记录是否有进行交换.值为false 进行了比较 true 则没有
代码如下
i:=1; repeat
bo:=true;
for j:=1 to 10-i
if a[j]<a[j+1] then
begin
temp:=a[j]; a[j]:=a[j+1];
a[j+1]:=temp;
bo:=false;
end;
inc(i);
until bo;
3.再次改进 如果说是有20个元素.数据序列是8,3,4,9,7再后跟着15个大于9且已经排好序的数据.在第三趟后算法终止.总共做了19+18+17=54次比较使得绝大多数已排好序的数据在一遍
扫描后足以发现他们是排好序的情况下仍然被检查3遍.
我们改进如下 flag:=10;
while flag>0 do
begin k:=flag-1; flag:=0;
for i:=1 to k do
if a[i]<a[i+1] then
begin
temp:=a[i];
a[i]:=a[i+1]; a[i+1]:=temp;
flag:=i;
end;
end;
改进的冒泡算法对上述数据进行的比较次数是19+4+2=24.
l 希尔排序 <1> 基本思想
希尔排序法是1959年由D.L.Shell提出来的,又称减少增量的排序。下表是以八个元素排序示范的例子.在该例中,开始时相隔4个成分,分别按组进行排序,这时每组2个成分,共4组;
然后相隔2个成分,在按组排序......最后,对所有相邻成分进行排序. 可参阅<<计算机程序设计技巧??第三卷排序查找
<2> 算法实现
j:=10;
i:=1;
while j>1 do
begin
j:=j div 2;
repeat
alldone:=true;
for index:=1 to 10-j do
begin
i:=index+j;
if a[index]<a[i] then
begin
temp:=a[index];
a[index]:=a[i];
a[i]:=temp;
alldone:=false;
end;
end;
until alldone
end;
//说句实话,这个很少有人用.:( 当然我也不会,书上抄的
l 插入排序
<1> 基本思想
//对不起,我没书.所以是我自己讲.我很菜.不要介意 插入排序的思想就是读一个,排一个. //也许是这样,起码我是这么认为的:)
将第1个数放入数组的第1个元素中,以后读入的数与已存入数组的数进行比较,确定它在从大到小的排列中应处的位置.将该位置以及以后的元素向后推移一个位置
,将读入的新数填入空出的位置中. <2> 算法实现 {加了读入语句}
procedure insert(x,num:integer);
var
i,pos:integer;
search:boolean;
begin
pos:=1;
search:=true;
while search and (pos<=num ) do
if x>a[pos]
then search:=fasle
else inc(pos);
for i:=num downto pos do
a[i+1]:=a[i];
a[pos]:=x;
num:=num+1;
end;
num:=0 {当前数组的长度}
for i:=1 to 10 do
begin
read(x);
intert(x,num)
end;
l 合并排序
<1> 基本思想
合并排序的算法就是二分法。 分解:将n个元素分解成各含 一半元素的子序列。 解决:用合并排序法对两个子序列递归地排序。 合并:合并两个已排序的子序列排序结果。 在对子序列排列时,当其长度为1时递归结束,因为单个元素被认为是已排好序的.合并排序的.合并排序的关键步骤在于合并目前产生的两个已排好序的子序列: A[p..q] 和 A[q+1…r]; 将它们合并成一个已排好序的子序列A[p..r]. 我们引入一个辅助过程merge(A,p,q,r)来完成这一项合并工作,其中A是数组,p,q,r是下标.
<2> 算法实现
procedure merge( p,q,r:integer); var i,j,t:integer; it:array[1..10] of integer; begin t:=p; i:=p; j:=q+1; while t<=r do begin if (i<=q) and ((j>j) or (a[i]<=a[j])) then begin it[t]:=a[i]; inc(i); end else begin it[t]:=a[j]; inc(j); end; inc(t); end; for i:=p to r do a[i]:=t[i]; end; procedure merge_sort(p,r:integer); var q:integer; begin if p<>r then begin q:=(p+r-1) div 2 ; merge_sort(p,q); merge_sort(q+1,r); merge(p,q,r); end; end; begin merge_sort(1,10); end. l 快速排序
<1> 基本思想
快速排序的基本思想是基于分治策略的。对于输入的子序列L[p..r],如果规模足够小则直接进行排序,否则分三步处理: 分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。 递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。 合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算L[p..r]就已排好序。 这个解决流程是符合分治法的基本步骤的。因此,快速排序法是分治法的经典应用实例之一。
================================================================================
排序算法是一种基本并且常用的算法。由于实际工作中处理的数量巨大,所以排序算法 对算法本身的速度要求很高。而一般我们所谓的算法的性能主要是指算法的复杂度,一般用
O方法来表示。在后面我将 给出详细的说明。 对 于排序的算法我想先做一点简单的介绍,也是给这篇文章理一个提纲。 我将按照算法的复杂度,从简单到难来分析算法。第一部分是简单排序算法,后面你将看到他们的共同
点是算法复杂度为O(N*N)(因为没有使用word,所 以无法打出上标和下标)。第二部分是高级排序算法,复杂度为O(Log2(N))。这里我们只介绍一种算法。另外还有几种算法因为
涉及树与堆的概念,所以 这里不于讨论。 第三部分类似动脑筋。这里的两种算法并不是最好的(甚至有最慢的),但是算法本身比较奇特,值得参考(编程的角度)。同时也可
以让我们从另外的角度来认识 这个问题。现在,让我们开始吧: 一、简单排序算法 由于程序比较简单,所以没有加什么注释。所有的程序都给出了完整的运行代码,并在我的VC环境 下运行通过。因为没有涉及MFC和WINDOWS的内容,所以在BORLAND C++的平台上应该也不会有什么 问题的。在代码的后面给出了运行过程示意,希望对理解有帮助。 1.冒泡法: 这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡: #include <iostream.h> void BubbleSort(int* pData,int Count) { int iTemp; for(int i=1;i<Count;i++) { for(int j=Count-1;j>=i;j--) { if(pData[j]<pData[j-1]) { iTemp = pData[j-1]; pData[j-1] = pData[j]; pData[j] = iTemp; } } } }
void main() { int data[] = {10,9,8,7,6,5,4}; BubbleSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; }
倒序(最糟情况) 第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次) 第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:6次 其他: 第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次) 第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:3次 上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换,显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,
为1+2+...+n-1。写成公式就是1/2*(n-1)*n。 现在注意,我们给出O方法的定义: 若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。(呵呵,不要说没学好数学呀,对于编程数学是非常重要的!!!)
现 在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以 f(n) =O(g(n))=O(n*n)。所以我们程序循环的复杂度为O(n*n)。再看交换。从程
序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实 交换本身同数据源的有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都
会交换),复杂度为O(n*n)。当数据为正序, 将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的原因,我们通常都是通过循环次数来对比算法。 2.交换法: 交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。 #include <iostream.h> void ExchangeSort(int* pData,int Count) { int iTemp; for(int i=0;i<Count-1;i++) { for(int j=i+1;j<Count;j++) { if(pData[j]<pData[i]) { iTemp = pData[i]; pData[i] = pData[j]; pData[j] = iTemp; } } } }
void main() { int data[] = {10,9,8,7,6,5,4}; ExchangeSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 倒序(最糟情况) 第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次) 第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:6次
其他: 第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次) 第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次) 第一轮:7,8,10,9->7,8,9,10(交换1次) 循环次数:6次 交换次数:3次
从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以只能直
接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。
3.选择法: 现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下)这种方法类似我们人为的排序习惯:从数据中选择最小的同第一个值交换,在从省下的部分中
选择最小的与第二个交换,这样往复下去。 #include <iostream.h> void SelectSort(int* pData,int Count) { int iTemp; int iPos; for(int i=0;i<Count-1;i++) { iTemp = pData[i]; iPos = i; for(int j=i+1;j<Count;j++) { if(pData[j]<iTemp) { iTemp = pData[j]; iPos = j; } } pData[iPos] = pData[i]; pData[i] = iTemp; } }
void main() { int data[] = {10,9,8,7,6,5,4}; SelectSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 倒序(最糟情况) 第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次) 第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次) 第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次) 循环次数:6次 交换次数:2次
其他: 第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次) 第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次) 第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次) 循环次数:6次 交换次数:3次 遗 憾的是算法需要的循环次数依然是1/2*(n-1)*n。所以算法复杂度为O(n*n)。我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最 小值)。所以f(n)<=n 所以我
们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。
4.插入法: 插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张 #include <iostream.h> void InsertSort(int* pData,int Count) { int iTemp; int iPos; for(int i=1;i<Count;i++) { iTemp = pData[i]; iPos = i-1; while((iPos>=0) && (iTemp<pData[iPos])) { pData[iPos+1] = pData[iPos]; iPos--; } pData[iPos+1] = iTemp; } }
void main() { int data[] = {10,9,8,7,6,5,4}; InsertSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; }
倒序(最糟情况) 第一轮:10,9,8,7->9,10,8,7(交换1次)(循环1次) 第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次) 第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次) 循环次数:6次 交换次数:3次
其他: 第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次) 第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次) 第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次) 循环次数:4次 交换次数:2次
上 面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中最好的,其实不是,因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的 结果可以看
出,循环的次数f(n)<= 1/2*n*(n-1)<=1/2*n*n。所以其复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单排序的不同,交换次数仍然 可以这样推导)。现在看
交换,从外观上看,交换次数是O(n)(推导类似选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们 需要三次‘=’ 而这里显然多了一些,所以
我们浪费了时间。
最终,我个人认为,在简单排序算法中,选择法是最好的。
二、高级排序算法: 高 级排序算法中我们将只介绍这一种,同时也是目前我所知道(我看过的资料中)的最快的。它的工作看起来仍然象一个二叉树。首先我们选择一个中间值 middle程序中我们使
用数组中间值,然后把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使用这个过程 (最容易的方法——递归)。 1.快速排序: #include <iostream.h> void run(int* pData,int left,int right) { int i,j; int middle,iTemp; i = left; j = right; middle = pData[(left+right)/2]; //求中间值 do{ while((pData[i]<middle) && (i<right))//从左扫描大于中值的数 i++; while((pData[j]>middle) && (j>left))//从右扫描大于中值的数 j--; if(i<=j)//找到了一对值 { //交换 iTemp = pData[i]; pData[i] = pData[j]; pData[j] = iTemp; i++; j--; } }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次)
//当左边部分有值(left<j),递归左半边 if(left<j) run(pData,left,j); //当右边部分有值(right>i),递归右半边 if(right>i) run(pData,i,right); } void QuickSort(int* pData,int Count) { run(pData,0,Count-1); } void main() { int data[] = {10,9,8,7,6,5,4}; QuickSort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; } 这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况 1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。 2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。 第一层递归,循环n次,第二层循环2*(n/2)...... 所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n 所以算法复杂度为O(log2(n)*n) 其 他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变成交换法(由于使用了递归,情况更糟)。但是你认为这种 情况发生的几率
有多大??呵呵,你完全不必担心这个问题。实践证明,大多数的情况,快速排序总是最好的。如果你担心这个问题,你可以使用堆排序,这是一种 稳定的O(log2(n)*n)算法,但
是通常情况下速度要慢于快速排序(因为要重组堆)。
三、其他排序 1.双向冒泡: 通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。 代码看起来复杂,仔细理一下就明白了,是一个来回震荡的方式。写这段代码的作者认为这样可以在冒泡
的基础上减少一些交换(我不这么认为,也许我错了)。反正我认为这是一段有趣的代码,值得一看。 #include <iostream.h> void Bubble2Sort(int* pData,int Count) { int iTemp; int left = 1; int right =Count -1; int t; do { //正向的部分 for(int i=right;i>=left;i--) { if(pData[i]<pData[i-1]) { iTemp = pData[i]; pData[i] = pData[i-1]; pData[i-1] = iTemp; t = i; } } left = t+1; //反向的部分 for(i=left;i<right+1;i++) { if(pData[i]<pData[i-1]) { iTemp = pData[i]; pData[i] = pData[i-1]; pData[i-1] = iTemp; t = i; } } right = t-1; }while(left<=right); }
void main() { int data[] = {10,9,8,7,6,5,4}; Bubble2Sort(data,7); for (int i=0;i<7;i++) cout<<data[i]<<" "; cout<<"\n"; }
2.SHELL排序 这个排序非常复杂,看了程序就知道了。 首先需要一个递减的步长,这里我们使用的是9、5、3、1(最后的步长必须是1)。工作原理是首先对相隔9-1个元素的所有内容排序,然
后再使用同样的方法对相隔5-1个元素的排序,以次类推。 #include <iostream.h> void ShellSort(int* pData,int Count) { int step[4]; step[0] = 9; step[1] = 5; step[2] = 3; step[3] = 1; int i,Temp; int k,s,w; for(int i=0;i<4;i++) { k = step[i]; s = -k; for(int j=k;j<Count;j++) { iTemp = pData[j]; w = j-k;//求上step个元素的下标 if(s ==0) { s = -k; s++; pData[s] = iTemp; } while((iTemp<pData[w]) && (w>=0) && (w<=Count)) { pData[w+k] = pData[w]; w = w-k; } pData[w+k] = iTemp; } } }
void main() { int data[] = {10,9,8,7,6,5,4,3,2,1,-10,-1}; ShellSort(data,12); for (int i=0;i<12;i++) cout<<data[i]<<" "; cout<<"\n"; } 呵 呵,程序看起来有些头疼。不过也不是很难,把s==0的块去掉就轻松多了,这里是避免使用0 步长造成程序异常而写的代码。这个代码我认为很值得一看。这个算法的得名是因
为其发明者的名字D.L.SHELL。依照参考资料上的说法:“由于复杂的数 学原因避免使用2的幂次步长,它能降低算法效率。”另外算法的复杂度为n的1.2次幂。同样因为非常复杂
并 “超出本书讨论范围”的原因(我也不知道过程),我们只有结果了.
|
请发表评论