数据结构中各种排序思路详解

来源:互联网 发布:淘宝收藏夹默认公开吗 编辑:程序博客网 时间:2024/06/05 10:40

排序:

Void X-Sort(ElementType A[],int N)

{
}

/*

1、 N基于正整数

2、 基于比较的排序(>=<有定义)

3、 只要讨论内部排序

4、 稳定性:任意两个相等的数据,排序前后相对位置不变

5、 X:排序算法的名称

*/

冒泡排序:

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序算法的运作如下:(从后往前)

1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

3. 针对所有的元素重复以上的步骤,除了最后一个。

4. 持续每次对越来越少的元素重复上面的步骤,直到

5. 没有任何一对数字需要比较

Void Bubble_Sort (ElementType A[],int N)

{

for(p=N-1;p>=0;p--)

{

flag=0;

//一趟冒泡,p表示最后一个元素的位置(N-1

for(i=0;i<p;i++)

{

if(A[i]>A[i++])

{

Swap(A[i],A[i+1]);

flag=1;//标志发生交换

}

}

if(flag==0) break;//全程无交换
}

}

最好情况是:顺序T=O(N)

最坏情况是:逆序T=O(N^2)

插入排序:

插入排序的思想有点像打扑克抓牌的时候,我们插入扑克牌的做法。想象一下,抓牌时,我们都是把抓到的牌按顺序放在手中。因此每抓一张新牌,我们都将其插入到已有的排好序的手牌当中,注意体会刚才的那句话。也就是说,插入排序的思想是,将新来的元素按顺序放入一个已有的有序序列当中。

 举个例子可能更容易理解一些,假设有这样一系列数字:

8 2 4 9 3 6  首先我们考虑数字2,假设后面的数字不存在(手中只有一张8,又抓来了2),那么显然2应该放在8的前面。

2 8 4 9 3 6  又抓来了一张4,现在大家都知道应该怎么办了吧?

2 4 8 9 3 6  又来了个9,没错,正好不用换顺序

2 4 8 9 3 6  同样的道理,考虑3该放的位置,显然放在2和4的中间

2 3 4 8 9 6  最后一个也是一样,最后得到从小到大的序列

2 3 4 6 8 9  完成排序

 

Void Insertion_Sort (Element Type A[],int N)

{

for (p=1;p<N;p++)

{

Tmp=A[p];//摸下一张牌,Tmp:临时位置

       for(i=p; i>0 && A[i-1]>Tmp;i--)

         A[i]=A[i-1];//向后移位,移出空位

A[i]=Tmp;//i手里的牌应该放的位置

}

}

 

希尔排序:

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。

2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

3. 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量  =1(  <  …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。

选择排序:

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。 

n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果:

①初始状态:无序区为R[1..n],有序区为空。

②第1趟排序

在无序区R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。

……

③第i趟排序

i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区


思路:对比数组中前一个元素跟后一个元素的大小,如果后面的元素比前面的元素小则用一个变量k来记住他的位置,接着第二次比较,前面“后一个元素”现变成了“前一个元素”,继续跟他的“后一个元素”进行比较如果后面的元素比他要小则用变量k记住它在数组中的位置(下标),等到循环结束的时候,我们应该找到了最小的那个数的下标了,然后进行判断,如果这个元素的下标不是第一个元素的下标,就让第一个元素跟他交换一下值,这样就找到整个数组中最小的数了。然后找到数组中第二小的数,让他跟数组中第二个元素交换一下值,以此类推。

快速排序:

快速排序(Quicksort)是冒泡排序的一种改进。

快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

c++中可以用函数qsort()可以直接为数组进行排序。

:

void  qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *));

参数:
1 待
排序数组首地址
2 数组中待排序元素数量
3 各元素的占用空间大小
4 指向函数的
指针,用于确定排序的顺序。

qsort 的使用方法:

注意qsort,即快速排序,包含在<cstdlib><stdlib.h>,
 函数有四个参数,没有返回值 下面是一个典型的写法:
 

qsort(s,n,sizeof(s[0]),cmp);


 1 s是需要排序的数组名也可以理解成开始地址,  
 因为你如果只需要对数组的部分排序的话, s可以写成&s[i]的形式的
 2参数n是参与排序的元素个数,第三个参数是单个元素的大小,
3 通常我们用sizeof()来获取大小因为它足够放心 绝对无毒^_^

4 cmp是一个比较函数 不过这不是系统定义好的函数 而是需要自己手动写的 也就是说 你可以把它叫做abcd或是rubbish什么
 因为是比较函数 我比较习惯用cmp
 

cmp的典型定义是                                     

 int cmp(const void *a,const void *b);

假设是对int排序的话,如果是升序,那么就是如果abcmp返回一个正值,小则负值,相等返回0

当然当你对具体的数组进行排序的话 cmp里面就要做具体的处理


一、对int类型数组排序

int num[100];

int cmp ( const void *a , const void *b )

{

  return *(int *)a - *(int *)b;  //升序排序

//return *(int *)b - *(int *)a; //降序排序

/*可见:参数列表是两个空指针,现在他要去指向你的数组元素。所以转型为你当前的类型,然后取值。

        升序排列时,若第一个参数指针指向的大于第二个参数指针指向的,则返回正;若第一个参数指针指向的等于第二个参数指针指向的,则返回零;若第一个参数指针指向的小于第二个参数指针指向的,则返回负。

        降序排列时,则刚好相反。

*/

}

qsort(s,n,sizeof(s[0]),cmp);

  

二、对char类型数组排序(同int类型)

char word[100];

int cmp( const void *a , const void *b )

{

return *(char *)a - *(char *)b;

}

qsort(word,100,sizeof(word[0]),cmp);

//附,可能 getchar();  会派上用场 

 

三、对double类型数组排序(特别要注意)

double in[100];

int cmp( const void *a , const void *b )

{

return *(double *)a > *(double *)b ? 1 : -1;

//返回值的问题,显然cmp返回的是一个整型,所以避免double返回小数而被丢失,用一个判断返回值。

}

qsort(in,100,sizeof(in[0]),cmp);

 //附:排序结果的输出,一般建议用“ %g ”格式

/* 在这里多嘴一句,"%g"格式输出 虽然书上是说系统会自动选择" %f "格式 " %e " 格式 中长度较短的格式,并去掉无意义的0,但实际上系统如果选择了" %e ",系统会输出比“ %e " 格式更省一位的格式输出。(此结论,来自VC6.0的实际操作)*/

 

/*四、对结构体一级排序

struct In

{

double data;

int other;

}s[100]

 

//按照data的值从小到大将结构体排序,关于结构体内的排序关键数据data的类型可以很多种,参考上面的例子写

 

int cmp( const void *a ,const void *b)

{

return (*(In *)a).data > (*(In *)b).data ? 1 : -1;

//注意,这条语句在VC6.0环境下运行可能会出错,但是并不是语句错了,而是你要先Build,或者全部重建。总之语句是对的。

//或者你可以将这上面1条语句改成下面这3条语句

//struct In *aa = (In *)a;
//struct In *bb = (In *)b;
//return aa->data > bb->data ? 1 : -1;

}

qsort(s,100,sizeof(s[0]),cmp);

 

五、对结构体二级排序

struct In

{

int x;   //你可以比喻成:失败次数

int y;   //你可以比喻成:成功次数

}s[100];

 

//按照x从小到大排序,当x相等时按照y从大到小排序。 你可以想象成:失败是主要因素的一个问题,先比较 失败次数少,失败次数相同 再看 成功次数多。

 

int cmp( const void *a , const void *b )

{

struct In *c = (In *)a;

struct In *d = (In *)b;

if(c->x != d->x) return c->x - d->x;

else return d->y - c->y;

}

qsort(s,100,sizeof(s[0]),cmp);  

 

六、对字符串进行排序

struct In

{

int data;

char str[100];

}s[100];

//按照结构体中字符串str的字典顺序排序

字典排序lexicographical order)是一种对于随机变量形成序列的排序方法。

其方法是,按照字母顺序,或者数字小大顺序,由小到大的形成序列。

 

int cmp ( const void *a , const void *b )

{

return strcmp( (*(In *)a)->str , (*(In *)b)->str );

}

qsort(s,100,sizeof(s[0]),cmp);

 */

归并排序:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

归并操作的工作原理如下:

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾

将另一序列剩下的所有元素直接复制到合并序列尾

PS:图是自己画的,图丑勿怪。哈哈~

 

2

4

6

9

15

25

 

1

5

7

13

21

30

 

1

2

4

5

6

7

9

13

15

21

25

30

 

注意:

1、 TmpA是一个临时数组,一个优秀的程序员一定会用完之后free(TmpA)

2、 因为需要申请新的空间,所以很浪费空间,一般内排序不建议用,外排序可以用

/*基数排序:

基数排序radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

第一步

LSD为例,假设原来有一串数值如下所示:

73, 22, 93, 43, 55, 14, 28, 65, 39, 81

首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:

0

1 81

2 22

3 73 93 43

4 14

5 55 65

6

7

8 28

9 39


第二步

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

81, 22, 73, 93, 43, 14, 55, 65, 28, 39

接着再进行一次分配,这次是根据十位数来分配:

0

1 14

2 22 28

3 39

4 43

5 55

6 65

7 73

8 81

9 93


第三步

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

14, 22, 28, 39, 43, 55, 65, 73, 81, 93

这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

计数排序:

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。

计数排序对输入的数据有附加的限制条件:

1、输入的线性表的元素属于有限偏序集S;

2、设输入的线性表的长度为n,|S|=k(表示集合S中元素的总数目为k),则k=O(n)。

在这两个条件下,计数排序的复杂性为O(n)。

计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。*/

 

 

原创粉丝点击