一、直接插入排序
- 基本思想:用我自己的话说,第一趟,前两个数比较大小,然后从小到大排序,(我的文中都是从小到大排序,从大到小排序都是一个道理);第二趟,拿起第三个数插到已经排好序的前两个数中,使他依然有序。第三趟,拿起第四个数插到已经排好序的前三个数中,使他依然有序。。。。。。好了,后面和前面一样,继续往下做就好。
- 我举个例子
- 初始状态:{3,6,4,2,11,10,6}
- 第一趟排序:{(3 , 6),4,2,11,10,6}
- 第二趟排序;{(3 , 4 , 6),2, 11 , 10 , 6}
- 第三趟排序:{(2 , 3 , 4 , 6),11 , 10 , 6}
- 第四趟排序:{(2 , 3 , 4 , 6 , 11),10 , 6}
- 第五趟排序:{(2 , 3 , 4 , 6 , 10 , 11), 6}
- 第六趟排序:{(2 , 3 , 4 , 6 , 6 , 10 , 11)}
- 好了,排序完成!
- 再来看看代码
void insertsort(keytype k[],int n)
{
int i,j;
keytype tmp;
for(i=2;i<=n;i++)
{
tmp=k[i];
j=i-1;
while(j>0 && tmp<k[j])
{
k[j+1]=k[j--];
}
k[j+1]=tmp;
}
}
二、选择排序
- 基本思想:举个例子就很明显了,来一个数组
a[10]={9,8,7,6,5,4,3,2,1,0};
,从小到大排序。 - 第一趟:从后10个元素(全部元素)中挑出一个最小的元素和第一个元素交换。因此把最小元素的位置确定。数组变成
a[10]={0,8,7,6,5,4,3,2,1,9};
- 第二趟:从后9个元素中挑出一个最小元素和数组第二个元素交换,因此把第二小元素的位置确定。数组变成
a[10]={0,1,7,6,5,4,3,2,8,9};
- 第三趟:从后8个元素中挑出一个最小元素和数组第三个元素交换,因此把第三小元素位置确定。数组变成
a[10]={0,1,2,6,5,4,3,7,8,9};
- 第四趟:从后7个元素中挑出一个最小元素和数组第四个元素交换,因此把第四小元素位置确定。数组变成
a[10]={0,1,2,3,5,4,6,7,8,9};
- 第五趟:从后6个元素中挑出一个最小元素和数组第五个元素交换,因此把第五小元素位置确定。数组变成
a[10]={0,1,2,3,4,5,6,7,8,9};
- 第六趟:发现数组已经有序。思想我说清楚了吧。
- 好,来看代码。
void selectsort(int a[], int n)
{
int i = 0, j, min, tmp;
for (i = 0; i < n-1; i++)
{
min = i;
for (j = i + 1; j < n ; j++)
{
if (a[j] < a[min])
{
min = j;
}
}
if (min != i)
{
tmp = a[min];
a[min] = a[i];
a[i] = tmp;
}
}
}
选择排序优化
- 上面的代码每次循环只是找到了他的最小元素,那么我们还可以优化。每次循环我们可以找到他的最小元素和最大元素,是不是遍历次数就少了一半呢。来看看代码
//选择排序优化
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void selectsortopt(int a[], int n)
{
int i=0, j, min=i, max=n-1, tmp;
n = n - 1;
while (i <= n)
{
min = i;
max = n;
for (j = i; j <= n; j++)
{
if (a[j] < a[min])
{
min = j;
}
if (a[j]>a[max])
{
max = j;
}
}
swap(&a[max], &a[n]);
if (min == n) //如果最大值在最小位置,最小值在最大位置
{
min = max;
}
swap(&a[min], &a[i]);
i++;
n--;
}
}
- 需要注意的是这个方法要注意一个问题。就是这个数组。
a[10]={9,8,7,6,5,4,3,2,1,0};
最大值在最小值的位置,最小值在最大值的位置。那么最小值和最大值经过两次交换之后,就会出现数组什么都没有变的情况。因此需要加判定条件的哦。(代码上我已注明)
三、冒泡排序
- 基本思想
- 将序列中的第一个元素与第二个元素进行比较,若前者大于后者,则将第一个元素与第二个元素进行位置交换,否则不交换。
- 再将第二个元素和第三个元素进行比较,同样若前者大于后者,则将第二个元素与第三个元素进行位置交换,否则不交换。
- 依次类推,直到将第n-1个元素与第n个元素进行比较为止。
- 上面这个过程称为第一趟冒泡排序。经过第一趟冒泡排序之后,将长度为n的序列中最大的元素置于序列的尾部,即第n个位置。
- 然后在对剩下的n-1个元素进行同样的操作,这就叫第二趟冒泡排序。经过了第二趟的冒泡排序,将剩下的n-1个元素中最大元素置于序列的n-1的位置上。
- 如此进行下去,当执行完第n-1趟的冒泡排序后,就可以将序列中剩下2个元素中的最大元素置于序列的第二个位置上,第一个位置上的元素就是该序列中最小的元素,这样冒泡排序就完成了。
- 说起来难,其实代码特别简单。
void bubblesort(int a[], int n)
{
int flag = 1;
for (int i = 0; i < n && flag == 1; i++)
{
flag = 0;
for (int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
{
int tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
flag = 1;
}
}
}
}
- 还有一点点的优化的哈,在算法中设置一个标志变量flag。规定当flag为1时,说明本趟排序中仍有元素交换的动作,因此还需进行下一趟的比较。当flag为0的时候,说明本趟排序中已经没有元素的交换,只有元素的比较,因此说明该序列已经按值有序,排序可以停止。上面的代码算法已经体现了这种优化。
四、希尔排序
- 基本思想
- 设定一个元素间隔增量 gap,将参加排序的序列按这个间隔gap从第一个元素开始依次划分成若干个子序列。
- 例如:现有一个序列{5,30,7,9,20,10};最开始gap=3,可将序列这样划分。
- 第一趟排序是在gap=3的基础上进行的。因此将原序列划分成三个子序列{5,9}、{30,20}、{7,10};经过排序后序列变成{5,20,7,9,30,10};
- 第二趟排序是在gap=2的基础上进行的,因此将第一趟排序后的序列划分成2个子序列{5,7,20}、{20,9,10},经过排序后的序列变成{5,9,7,10,30,20};
- 在第三趟排序中gap=1,因此将第二趟排序后的序列划分成一个子序列,即序列本身,经过排序后序列变成为{5,7,9,10,20,30};
- 接下来,有一个问题就是我们如何选取间隔数gap的值呢?这是一个比较复杂的问题,因为它涉及到一些数学上尚未解决的问题。但是经验告诉我们一种比较常用的并且效果比较好的gap值得选取方法:首先gap取值为序列长的一半。而在后续的排序过程中,后一趟排序的gap取值为前一趟排序的gap取值的一般即可。
- 上面的叙述当中,希尔排序过程采取每次增量减一的方法,这样做不一定高效,因为增量缩小的速度太低,这里只是为了说明希尔排序的步骤。
- 来来来,说不明白,看看代码。
void shellsort(int a[], int n)
{
int gap = n, flag = 1, i, j;
while (gap > 1)
{
gap = gap / 2;
do
{
flag = 0;
for (i = 0; i < n - gap; i++)
{
j = i + gap;
if (a[i]>a[j])
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
flag = 1;
}
}
} while (flag != 0);
}
}
- 同样的,在子序列中采用的其他的排序方法,我这里采用的是冒泡排序,那么就要记得冒泡排序的优化的哦。flag值的标记的哦。
五、快速排序
- 快速排序被认为是目前最好的一种排序方法,那么你看要不要认真学一下。
- 基本思想:假设原序列为:
{5,7,4,2,11,10,6}
- 首先设置两个变量
i
和j
。i=1
,指向第一个元素5;j=7,
指向最后一个元素6。设定基准元素为i
指向的元素5。 - (1)反复执行
i=i+1
的操作,直到i
指向的元素大于等于基准元素5,或者i
指向序列尾部,即i=7
为止。然后反复执行j=j-1
的操作,直到j
所指向的元素小于等于基准元素5,或者j
指向序列的首部,即j=1
为止。按上述操作执行完毕,序列的状态变为i=2
指向第二个元素7,j=4
指向第四个元素2。 - (2)若此时
i<j
,则将i
与j
指向的元素进行交换。交换完的序列为{5,2,4,7,11,10,6};
- 现在完成第一次交换。然后重复执行步骤(1)、(2),直到
i≥j
为止,在执行步骤(3)。 - (3)此时
i≥j
,然后将基准元素与j
指向的元素交换位置,序列变成{4,2,5,7,11,10,6}
。 - 至此完成了原序列的第一次划分,此时序列中的基准元素5已经移至排序的最终位置,基准元素5的前半序列都小于5,后半序列都大于5.
- 接下来分别对基准元素5前后的子序列中长度大于1的子序列重复执行上述操作,到整个序列排序有序。
- 来来来,看描述看烦了,那就来看代码。
void quicksort(int a[], int s, int t)
{
int i, j;
if (s < t)
{
i = s;
j = t+1;
while (1)
{
do
{
i++;
} while (!(a[s] <= a[i] || i == t));
do
{
j--;
} while (!(a[s] >= a[j] || j == s));
if (i < j)
swap(&a[i], &a[j]); //交换函数
else
break;
}
swap(&a[s], &a[j]);
quicksort(a, s, j - 1); //基准元素前的子序列的递归
quicksort(a, j + 1, t); //基准元素后的子序列的递归
}
}
六、堆排序
- 我先来介绍介绍什么是堆。具有n个数据元素的序列
{k1,k2,k3...kn}
,当且仅当满足条件 1、Ki≥K2i Ki≥K2i+1 或者2、Ki≤K2i Ki≤K2i+1 (i=1,2,3…[n/2]);时,称该序列为一个堆。满足条件1的堆称为大顶堆,满足条件2的堆称为小顶堆。我们下面的讨论完全是基于大顶堆的。 - 堆排序算法的核心思想:
1、将原始序列构成一个堆。(建立初始堆)
2、交换堆的第一个元素和堆的最后一个元素的位置。
3、将移走最大值元素之后的剩余元素所构成的序列在转换成一个堆。 - 经过上述操作,就可将一个无序的序列从小到大进行排序。这种排序方法就是堆排序。
- 那么问题就集中在两个方面:(1)如何将原始序列构成一个堆;(2)如何将移走最大值元素之后的剩余元素所构成的序列在转换为一个堆。
下面来举个例子来说明将一个原始序列
{23,6,77,2,60,10,58,16,48,20}
。其对应的二叉树如图a,将其调整为一个堆的过程如果b,c,d,e,f。得到的堆序列为{77,60,58,48,20,10,23,16,2,6}
。
好了,来看看代码
void adjust(int a[], int i, int n)
{
int j;
int tmp;
tmp = a[i];
j = 2 * i + 1;
while (j <= n && j >= 0)
{
if (j < n && a[j] < a[j + 1])
{
j++;
}
if (tmp >= a[j])
{
break;
}
else
{
a[(j - 1) / 2] = a[j];
j = 2 * j + 1;
}
a[(j - 1) / 2] = tmp;
}
}
- 上面就是我们讨论的是第一个问题。交换第一个元素和新堆的最后一个元素的位置,也就是将最大的元素移至新堆的最后;调用adjust()函数将树根的元素向下调整,将除了最后一个元素的其他元素调整成一个新的大顶堆;重复上述操n-1次,就可以将一个无穷的序列堆排序为一个有序的序列。
- 这里得出堆排序的算法描述:
void heapsort(int a[], int n)
{
int i;
int tmp;
for (i = (n - 1) / 2; i >= 0; i--)
adjust2(a, i, n);
for (i = n - 1; i >= 0; i--)
{
tmp = a[i + 1];
a[i + 1] = a[0];
a[0] = tmp;
for (int j = (i - 1) / 2; j >= 0; j--)
adjust2(a, j, i);
}
}
- 注意:堆排序是针对线性序列的排序,之所以要采用完全二叉树的形式解释堆排序的过程,是出于方便的需要。
各种排序算法性能比较
- 还有好多排序方法,我在这儿先列举这几种。以后再补上吧。有问题的地方还请大佬指出。