一、导论
二、排序算法的原理和分类
三、冒泡排序法
四、选择排序法
五、插入排序法
六、希尔排序法
七、快速排序法
八、基数排序法
一、导论
排序算法可以说是所有算法的基础,基本上无论是大公司或者是小公司,只要写代码肯定要写这些代码,笔者最近在闲暇之时研究了六种比较经典的排序算法,于是决定拿出来跟大家一起分享一下。
二、排序算法的原理和分类
排序算法大体来说可以分成两大类,其中第一类就是通过比较的方法,这种方法比较常见的有选择,冒泡,插入排序,希尔排序等诸多方法,第二类就是不是通过元素与元素比较的方法,其中比较常用的就是基数排序法。
一、冒泡排序法
冒泡排序法,顾名思义,就是通过与相邻比较的方法把最小的(最大的)数字浮到最边上,换句话说,代码采用的是和相邻的部分相比较就可以了。当然了,每次比较完成之后,最后一位就不需要进行比较了。当然,冒泡排序法有一个坑,这个我之前面试的时候踩过,所以这里要跟大家说一下。
void bubbleSequence(int *a, int numsSize)
{
for (int i=0;i<numsSize-1;i++) {
for (int j=0;j<numsSize-1-i;j++) {
if (a[j] < a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
一、选择排序法
选择排序法,顾名思义,就是首先找到全数组中最小的(最大的)的元素,然后将其调整到边上的位置,依次类推。那么如何找到最小的(最大的)元素呢?因为这里存在比较,因此可想而知需要设置两个指针来进行比较。通过第一个数字和第二个数字来进行比较最后来得到相应的元素。
void chooseSequence(int *a, int numsSize)
{
for (int i=0;i<numsSize;i++) {
for (int j=i+1;j<numsSize;j++) {
if (a[i]<a[j]) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
二、插入排序法
插入排序法,顾名思义,就是找到一个有序数列进行插入设计,那么如何找有序数列呢?我们可以把第一个元素当成一个有序数列,然后从第二个元素开始进行插入,它的基本算法流程是这样的:首先设置成一个临时变量用来保存这个元素,然后每次与这个元素进行比较,如果比这个元素大,那么就把这个值向右移动,一直到小于这个值或者到尾端那么此时将这个临时变量进行插入。
void insertSequence(int *a, int numsSize)
{
int j=0;
for (int i=0;i<numsSize;i++) {
int current = a[i];
for (j=i-1;(current<a[j]) && (j>=0);j--) {
a[j+1] = a[j];
}
a[j+1] = current;
}
}
三、希尔排序法
希尔排序法可以说是插入排序法的一个增量部分,两者的区别在于,插入排序法从前到后一个一个值进行插入,而希尔排序法会设置一个增量,这个增量类似于一个间隔,我们首先按照这个增量来进行插入排序(为啥用插入排序我也不知道),然后不断的缩小间隔,一直到increment=1时我们认为此时的插入排序就是最后的一个排序序列。这个排序算法最重要的就是间隔的确定,间隔的大小决定了算法的执行速度。
void shellSequence(int *a, int numsSize)
{
int increment = numsSize / 3;
do {
for (int i=increment+1;i<numsSize;i++) {
int j = i - increment;
int current = a[i];
while (j >= 0 && current < a[j]) {
// printf("a[j] is %d ",a[j]);
a[j+increment] = a[j];
j = j - increment;
}
a[j+increment] = current;
}
increment = increment - 1;
}while (increment >= 1);
}
四、快速排序法
快速排序法有点类似于选择排序法,两者的共同点在于两者都要找到一个值进行比对,区别在于选择排序法是找到最大值,而快速排序法是找到其中的中间值,然后根据这个中间值将序列分成左侧和右侧两个部分。其算法流程可以分成两个部分,分别是填充数据+分冶法。先说第一部分填充数据法,这部分一共可以分成两个部分,首先我们需要在这里设置一个高位指针和低位指针,首先我们要找到一个高位指针,如果说我们的这个值大于我们的基准值那么我们就跳过他高位指针,继续向低走,如果小于我们的基准值,那么我们就需要把它移动到低位来填充数据,接着我们将低位指针向上移动一个单位,同样判断当前值是否小于基准值,如果小于基准值,那么我们就需要将其指针向上移动一个单位,反之我们就需要把当前的低位值移动到高位。依次类推,一直到结束。这样就完成了填充数据法,之后我们继续判断左侧和右侧的部分同样按照上述方法填充数据并进行判断,知道左侧指针和右侧指针相等,此时结束,排序随之完成。
void quickSequence(int *a, int low, int high)
{
if (low < high) {
int index = getIndex(a, low, high);
quickSequence(a, low, index-1); // ?index
quickSequence(a, index+1, high);
}
}
int getIndex(int *a, int low, int high)
{
int tmp = a[low];
while (low < high) {
while (low < high && a[high] >= tmp) {
high--;
}
a[low] = a[high];
while (low < high && a[low] <= tmp) {
low++;
}
a[high] = a[low];
}
a[low] = tmp;
return low;
}
这也是在这其中唯一用到递归的排序方法。
五、基数排序法
基数排序法是桶装排序法的一种升级排序法,它有两种排序方法,分别为LSB和MSB两种,两种排序的方法差不多,我们这里只说LSB排序法。先说一下LSB排序的基本算法要求:他是以最小序列为基础的,我们首先按照个位的顺序来进行排列,按照个位首先对其进行一次排序,得到一个新的序列,然后按照十位进行排序,依此类推,直到最高位来进行排序。所以这里的核心算法为如何通过个位来对其进行排序,其算法顺序如下:
准备工作:1、需要准备一个数组,确定各位的数字到底有几个。我们这里设置成count
2、需要准备一个数组,用来记录个位数字排列完成以后的结果。我们这里设置成bucket
正式开始:1、需要将count数组里面的东西全部清空(这个主要是因为后续10位百位也需要用,所以也是需要的)。
2、通过函数得到其个位的结果。并且通过count来得到每一位到底有多少个。
3、通过叠加的方法来得到相应的最大位数(这个是用来确定边界)。
4、通过位数的相关边界来得到相应的结果(至于为什么要用反向我不太清楚)
5、将bucket重新输入会data函数中。
void bucketSequence(int *a, int start, int end)
{
// set count
int count[10];
for (int d=0;d<2;d++) {
for (int i=0;i<10;i++) {
count[i] = 0;
}
// put a into count
for (int index = start;index<=end;index++) {
int m = getdigit(a[index], d);
printf("m is %d ", m);
count[m]++;
}
int *bucket = (int *)malloc(sizeof(int) * (end-start));
// add count
for (int i=1;i<10;i++) {
count[i] = count[i] + count[i-1];
}
// put count into bucket
for (int i=end;i>=start;i--) {
int j = getdigit(a[i], d);
bucket[count[j]-1] = a[i];
--count[j];
}
for (int i=start;i<=end;i++) {
printf("bucket is %d ",bucket[i]);
}
int k = 0;
int l = 0;
while ((k<=end) && (l<=end)) {
a[k] = bucket[l];
k++;
l++;
}
free(bucket);
}
}
int getdigit(int arr,int d)
{
int a[] = {1, 10};
return ((arr/a[d]) % 10);
}
最后说一下,在研究算法的时候一定要有一只笔和一张纸,边看算法边在草纸上把过程写出来,这样能够更快的理解算法。