前言
数据结构中的排序分为内排序和外排序。其中内排序是指将排序记录存放在计算机随机存储器(内存)中进行的排序过程,而外部排序因为待排序记录数量很大,以至于内存不能一次容纳全部记录,所以在排序过程中需要对外部存储器进行访问的排序过程。
在衡量两者排序的效率上,内部排序衡量的标准为时间复杂度,外部排序衡量的是读写外存的次数。
我们耳熟能详的八大排序算法就属于内排序。如下图所示:
下面我们将使用python以及C++分别来实现这些排序算法。
直接插入排序
直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。因此该算法的时间复杂度达到了。
因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:
- 第一层循环:遍历待比较的所有数组元素
- 第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
如果:selected > ordered,那么将二者交换
代码实现:
python 版本:
def insert_sort(List):
#遍历数组中的所有元素,其中0号索引元素默认已排序,因此从1开始
for index in range(1,len(List)):
#将该元素与已排序好的前序数组依次比较,如果该元素小,则交换
for each in range(0,index):
if List[index-each-1] > List[index-each]:
temp = List[index-each]
List[index-each] = List[index-each-1]
List[index-each-1] = temp
return List;
C++ 版本:
void insert_sort(int array[], int n)
{
int i, j;
int temp;
for (i = 1; i < n; i++)
{
for (j = i; j > 0; j--) {
if (array[j - 1] > array[j]) {
temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
希尔排序
希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。 其复杂度也为.
代码实现:
python版本:
def shell_sort(List):
gap = int(len(List)/2)
while(gap>=1):
for each in range(0,len(List)-gap+1):
if List[each]>List[each+gap]:
temp = List[each+gap]
List[each+gap] = List[each]
List[each] = temp
gap = int(gap/2)
return List
C++版本:
void shell_sort(int array[], int n)
{
int temp;
int gap = n / 2;
while (gap >= 1) {
for (int i = 0; i < n - gap; i++) {
if (array[i] > array[i + gap]) {
temp = array[i + gap];
array[i + gap] = array[i];
array[i] = temp;
}
}
gap = gap / 2;
}
}
简单选择排序
简单选择排序的基本思想:比较+交换。
代码实现:
python版本 :
- 从待排序序列中,找到关键字最小的元素;
- 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
- 从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
因此我们可以发现,简单选择排序也是通过两层循环实现。
第一层循环:依次遍历序列当中的每一个元素
第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
def single_select_sort(List):
for index in range(0,len(List)):
minNum = List[index]
for each in range(index+1,len(List)):
if List[each] < minNum:
temp = List[each]
List[each] = minNum
minNum = temp
List[index] = minNum
return List
C++版本:
void singel_select_sort(int array[], int n)
{
int temp;
for (int i = 0; i < n; i++) {
int minNum = array[i];
for (int j = i + 1; j < n; j++) {
if (array[j] < minNum) {
temp = array[j];
array[j] = minNum;
minNum = temp;
}
}
array[i] = minNum;
}
}
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构:
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
ok,了解了这些定义。接下来,我们来看看堆排序的基本思想及基本步骤
基本思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
基本步骤:
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
a.假设给定无序序列结构如下
2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
此时,我们就将一个无需序列构造成了一个大顶堆。
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
a.将堆顶元素9和末尾元素4进行交换
b.重新调整结构,使其继续满足堆定义
c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
再简单总结下堆排序的基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
代码实现:
python版本
def swap(a, b): # 将a,b交换
temp = a
a = b
b = temp
return a,b
def adjust_heap(array, start, end):
"""
调整成大顶堆,初始堆时,从下往上;交换堆顶与堆尾后,从上往下调整
:array: 列表的引用
:start: 父结点
:end: 结束的下标
:return: 无
"""
while True:
# 当列表第一个是以下标0开始,结点下标为i,左孩子则为2*i+1,右孩子下标则为2*i+2;
left_child = 2*start + 1 # 左孩子的结点下标
if left_child > end:
break
# 寻找左右节点最大的节点
if left_child+1 <= end and array[left_child+1] > array[left_child]:
left_child += 1
if array[left_child] > array[start]: # 当左右孩子的最大值大于父结点时,则交换
array[left_child], array[start] = swap(array[left_child], array[start])
start = left_child # 交换之后以交换子结点为根的堆可能不是大顶堆,需重新调整
else: # 若父结点大于左右孩子,则退出循环
break
#print(">>", array)
def heap_sort(array): # 堆排序
# 先初始化大顶堆
first = len(array)//2 -1 # 最后一个有孩子的节点(//表示取整的意思)
for i in range(first, -1, -1): # 从最后一个有孩子的节点开始往上调整
#print(array[i])
adjust_heap(array, i, len(array)-1)
#print("初始化大顶堆结果:", array)
# 交换堆顶与堆尾
for head_end in range(len(array)-1, 0, -1): # start stop step
array[head_end], array[0] = swap(array[head_end], array[0]) # 交换堆顶与堆尾
adjust_heap(array, 0, head_end-1) # 堆长度减一(head_end-1),再从上往下调整成大顶堆
C++版本:
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
void adjust_heap(int array[], int start, int end) {
while(true) {
int left_child = 2 * start + 1;
if (left_child > end)
break;
if (left_child + 1 <= end && array[left_child + 1] > array[left_child])
left_child += 1;
if (array[left_child] > array[start]) {
swap(array[left_child], array[start]);
start = left_child;
}
else
break;
}
}
void heap_sort(int array[],int n) {
int first = n / 2 - 1;
for (int i = first; i >= 0; i--)
adjust_heap(array, i, n - 1);
for (int j = n - 1; j >= 0; j--) {
swap(array[j], array[0]);
adjust_heap(array, 0, j - 1);
}
}
小结
本篇文章主要介绍了内排序中的前四中排序算法,分别为插入排序,希尔排序,简单选择排序,以及堆排序,在下一章中,我们将继续学习剩下的四种排序算法。
ps:参考博客地址
https://blog.csdn.net/u013384984/article/details/79496052
https://www.cnblogs.com/0zcl/p/6737944.html
https://www.cnblogs.com/chengxiao/p/6129630.html