一、算法评价
-
操作时间:关键字的比较和数据元素的移动
-
空间:执行算法所需要的辅助存储空间
-
排序算法的稳定性:简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。
-
排序算法稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。
-
综合图
二、冒泡排序
- 主要思路:它是一种自底而上的排序方法,每次我们都从底层(第一个元素出发)依次比较相邻两个元素,如果他们的 顺序错误就把他们调换过来,每走完一趟就会有一个最大或是最小的元素被“浮”出来,下一次走就不用管这个 浮出来的已经成功被排序的元素了,重复地走访过要排序的元素,直到没有元素再需要交换,排序完成。
具体代码
void bubblesort(int *a, int length)
{
int temp;
for (int i = 0; i < length-1; i++)
{
for (int j = 0; j < length - i - 1; j++)
{
if (a[j] > a[j + 1])
{
temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
}
}
}
Printarray(a,length);
}
三、插入排序
- 主要思路:不停的将待排序的数值插入到已经排好的有序的序列中,使有序的序列不断壮大,直到所有的都排入了有序序 列。
- 它的工作原理非常类似于我们抓扑克牌,对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。
具体过程
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果有序序列中的这个元素大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5,直到全部排好序
具体代码
void insertsort_naive(int *a, int length)
{
for (int i = 1; i < length; i++)
{
int temp = a[i]; //被新抓到的扑克牌
for (int j = i-1; j>=0&&temp<a[j]; j--) //从它的前一张开始,如果比他大,就要给他让地方。
{
a[j+1] = a[j]; //第一次的j+1其实是i
a[j] = temp;
}
}
Printarray(a, length);
}
四、二分插入排序
- 主要思路:为了将插入变得简单,在拿新的元素往前插入的时候先和中间元素比较,注意这里是如果数据偏右,left+;偏 左, right-,mid是随之变化的,并非mid在加加减减,最后的left就是要插入的位置。
具体代码
void insertsort_half(int *a, int length)
{
for (int i = 1; i < length; i++)
{
int temp = a[i]; //被新抓到的扑克牌
// 拿在左手上的牌总是排序好的,所以可以用二分法
int left = 0;
int right = i - 1; // 手牌左右边界进行初始化
while (left <= right) // 采用二分法定位新牌的位置
{
int mid = (left + right) / 2;
if (a[mid] >temp)
right = mid - 1;
else
left = mid + 1;
}
//循环后的left结果就是要插入的元素位置 left元素以及它后面的元素都要向后移
for (int j = i - 1; j>=left; j--) //从它的前一张开始,如果比他大,就要给他让地方。
{
a[j + 1] = a[j]; //第一次的j+1其实是i
}
a[left] = temp;
} Printarray(a, length);
}
五、希尔排序
- 主要思路:希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
- 希尔排序的划分子序列不是像归并排序那种的二分,而是采用的叫做增量的技术,例如有十个元素的数组进行希尔排序,首先选择增量为10/2=5,此时第1个元素和第(1+5)个元素配对成子序列使用插入排序进行排序,第2和(2+5)个元素组成子序列,完成后增量继续减半为2,此时第1个元素、第(1+2)、第(1+4)、第(1+6)、第(1+8)个元素组成子序列进行插入排序。
- 这种增量选择方法的好处是可以使数组整体均匀有序,尽可能的减少比较和移动的次数,二分法中即使前一半数据有序,后一半中如果有比较小的数据,还是会造成大量的比较和移动,因此这种增量的方法和插入排序的配合更佳。
具体示例
- 对于100个数,先把它分为50组,每组两个数字,分别排序
- 第二轮排序再把经过第一轮排序后的100个数字分为25组,每组4个数,分别排序。
- 照这样子分下去,直到最后一轮分为1组,而此时这个序列已经大部分有序了。
具体代码
void Shellsort(int *a, int length)
{
//将很多个数分为gap组,这样每组里就只有length/gap个数字,直到后来变成一组。
int gap;//元素与它组内离他最近的元素之间的序号之—增量gap。
for (gap = length / 2; gap>= 1; gap=gap/2)//并逐步缩小增量
{
int j;
for (int i = gap; i < length; i++) //插入排序的一轮
{
int temp = a[i];
for ( j = i - gap; (j >= 0) && (a[j] > temp); j = j - gap)
{
a[j + gap] = a[j];
}
a[j + gap] = temp;
}
}
Printarray(a, length);
}
六、选择排序
- 主要思路:从整个序列中选出最小的(或者最大的)元素送到最前面,在余下的序列中在选出第二小的送到第二个位置。 以此类推,一直向后。
具体代码
void selectsort(int *a, int length)
{
int min;
for (int i = 0; i < length - 1; i++)
{
min = i;
for (int j = i+1; j < length; j++)// 未排序序列
{
if (a[j] < a[min]) // 找出未排序序列中的最小值
min = j;
}
// 放到已排序序列的末尾
if (min != i)
{
int temp;
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
Printarray(a, length);
}
算法评价
- 选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。