几种常见排序算法
1冒泡排序(BubbleSort)
冒泡排序思路:
1. 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;(第一轮结束后,序列最后一个元素一定是当前序列的最大值;)
2. 对序列当中剩下的n-1个元素再次执行步骤1。
3. 对于长度为n的序列,一共需要执行n-1轮比较
代码实现:
for (i= 0; i < n; i++)
{
for (j = 0; j < n - i; j++)
if (a[j] > a[j+1])
{
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
}
冒泡排序是稳定的。算法时间复杂度是O(n^2),空间复杂度O(1).
2直接选择排序(SelectionSort)
直接选择排序的基本思想:比较+交换。
1. 从待排序序列中,找到关键字最小的元素;
2. 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
3. 从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
选择排序通过两层循环实现。
第一层循环:依次遍历序列当中的每一个元素
第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
直接选择排序是不稳定的:算法的时间复杂度为O(n^2),空间复杂度是O(1).
代码实现:
def select_sort(L):
#依次遍历序列中的每一个元素
for x inrange(0,len(L)):
#将当前位置的元素定义此轮循环当中的最小值
minimum =L[x]
#将该元素与剩下的元素依次比较寻找最小元素
for i inrange(x+1,len(L)):
if L[i]< minimum:
temp = L[i];
L[i] = minimum;
minimum = temp
#将比较后得到的真正的最小值赋值给当前位置
L[x] =minimum
3直接插入排序(InsertionSort)
直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
直接插入排序是稳定的。算法时间复杂度是O(n^2),空间复杂度是O(1).
直接插入排序可以用两个循环完成:
1. 第一层循环:遍历待比较的所有数组元素
2. 第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
如果:selected > ordered,那么将二者交换
代码实现:
definsert_sort(L):
#遍历数组中的所有元素,其中0号索引元素默认已排序,因此从1开始
for x inrange(1,len(L)):
#将该元素与已排序好的前序数组依次比较,如果该元素小,则交换
#range(x-1,-1,-1):从x-1倒序循环到0
for i inrange(x-1,-1,-1):
#判断:如果符合条件则交换
ifL[i] > L[i+1]:
temp = L[i+1]
L[i+1] = L[i]
L[i] = temp
4堆排序
堆排序是一种树形选择排序,在排序过程中,将A[n]看成是完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。
堆排序是不稳定的。算法时间复杂度O(nlog n),空间复杂度是O(1).
1.首先将序列构建称为大顶堆;
2.取出当前大顶堆的根节点,将其与序列末尾元素进行交换;
3.对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质;
4.重复2.3步骤,直至堆中只有1个元素为止
代码实现:
void HeapSort(RecordType r[],int length)
{ crt_heap( r, length);n= length;
for ( i=n ; i>= 2 ; --i)
{
b=r[1]; /* 将堆顶记录和堆中的最后一个记录互换 */
r[1]= r[i] r[i]=b;
sift(r,1,i-1) ; /* 进行调整,使r[1..i-1]变成堆 */
}
} /* HeapSort */
5归并排序
采用分治法算法,归并排序实际上就是两个操作,拆分+合并,
分解----将序列每次折半拆分
合并----将划分后的序列段两两排序合并
归并排序是稳定的:其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlog2n), 空间复杂度是O(1).
合并算法实现:
void Merge ( RecordType r1[], int low, int mid, int high, RecordType r[])
{
i=low;j=mid+1; k=low;
while ( (i<=mid)&&(j<=high))
{if ( r1[i].key<=r1[j].key )
{
r[k]=r1[i] ;
++i;
}
else
{
r[k]=r1[j] ;
++j;
}
++k ;
}
if ( i<=mid )
r[k..high] =r1[i..mid];
if ( j<=high )
r[k..high] =r1[j..high];
}
6快速排序
快速排序是对冒泡排序的一种本质改进。
它的基本思想(挖坑填数+分治法)是通过一趟扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理它左右两边的数,直到基准点的左右只有一个元素为止。
用伪代码描述如下:
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中
快速排序是不稳定的。最理想情况算法时间复杂度O(nlog2n),最坏O(n ^2),空间复杂度为O(nlog2n)。
代码实现:
int quicksort(vector<int> &v, int left, int right){ if(left < right){ int key = v[left]; int low = left; int high = right; while(low < high){ while(low < high && v[high] > key){ high--; } v[low] = v[high]; while(low < high && v[low] < key){ low++; } v[high] = v[low]; } v[low] = key; quicksort(v,left,low-1); quicksort(v,low+1,right); } }
7希尔排序 (shell)
在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。
希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
希尔排序是稳定的:算法时间复杂度是O(n^2),空间复杂度是O(1).
希尔排序的总体实现应该由三个循环完成:
1. 第一层循环:将gap依次折半,对序列进行分组,直到gap=1
2. 第二、三层循环:也即直接插入排序所需要的两次循环。具体描述见上。
代码实现:
def insert_shell(L):
#初始化gap值,此处利用序列长度的一般为其赋值
gap = (int)(len(L)/2)
#第一层循环:依次改变gap值对列表进行分组
while (gap >= 1):
#下面:利用直接插入排序的思想对分组数据进行排序
#range(gap,len(L)):从gap开始
for x in range(gap,len(L)):
#range(x-gap,-1,-gap):从x-gap开始与选定元素开始倒序比较,每个比较元素之间间隔gap
for i in range(x-gap,-1,-gap):
#如果该组当中两个元素满足交换条件,则进行交换
if L[i] > L[i+gap]:
temp = L[i+gap]
L[i+gap] = L[i]
L[i] =temp
#while循环条件折半
gap = (int)(gap/2)