1.常见的排序算法
插入排序、选择排序、冒泡排序、快速排序、堆排序、归并排序、希尔排序、二叉树排序、计数排序、桶排序、基数排序
2.各种排序算法的时间复杂度和稳定性
3.算法详解
冒泡排序
(1) 原理:依次比较相邻的两个数,将值大/小的元素交换到右边。
(2) 思路:让数组当中相邻的两个数进行比较,数组当中比较小的数值向下沉,数值比较大的向上浮!外层for循环控制循环次数,内层for循环控制相邻的两个元素进行比较。
(3) 代码:
public static void BubbleSort(int [] arr){
int temp;//临时变量
for(int i=0; i<arr.length-1; i++){ //表示趟数,一共arr.length-1次。
for(int j=arr.length-1; j>i; j--){
if(arr[j] < arr[j-1]){
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
}
插入排序
(1) 原理:在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
(2) 思路:遍历数组,遍历到i时,a0,a1…ai-1是已经排好序的,取出ai,从ai-1开始向前和每个比较大小,如果小于,则将此位置元素向后移动,继续先前比较,如果不小于,则放到正在比较的元素之后。可见相等元素比较是,原来靠后的还是拍在后边,所以插入排序是稳定的。
(3) 代码:
void insertion_sort (int a[], int n) {
int i,j,v;
for (i=1; i<n; i++) { //如果第i个元素小于第j个,则第j个向后移动
for (v=a[i], j=i-1; j>=0&&v<a[j]; j--)
a[j+1]=a[j];
a[j+1]=v;
}
}
选择排序
(1) 原理:其实选择排序可以看成冒泡排序的优化,因为其目的相同,只是选择排序只有在确定了最小数的前提下才进行交换。
(2) 思路:在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换;
第二次遍历n-2个数,找到最小的数值与第二个元素交换;
(3) 代码:
// 选择排序
public void selectSort(int[] nums) {
if (nums == null || nums.length == 0)
return;
int minIndex;
// 只需要循环 n-1 次
for (int i = 0; i < nums.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
swap(nums, i, minIndex);
}
}
}
快速排序
(1) 原理:说白了就是给基准数据找其正确索引位置的过程。
(2) 思路:快速排序首先找到一个基准,下面程序以第一个元素作为基准(pivot),然后先从右向左搜索,如果发现比pivot小,则和pivot交换,然后从左向右搜索,如果发现比pivot大,则和pivot交换,一直到左边大于右边,此时pivot左边的都比它小,而右边的都比它大,此时pivot的位置就是排好序后应该在的位置,此时pivot将数组划分为左右两部分,可以递归采用该方法进行。快排的交换使排序成为不稳定的。
(3) 代码:
public static void quickSort(int a[],int l,int r){
if(l>=r)
return;
int i = l; int j = r; int key = a[l];//选择第一个数为key
while(i<j){
while(i<j && a[j]>=key)//从右向左找第一个小于key的值
j--;
if(i<j){
a[i] = a[j];
i++;
}
while(i<j && a[i]<key)//从左向右找第一个大于key的值
i++;
if(i<j){
a[j] = a[i];
j--;
}
}
//i == j
a[i] = key;
quickSort(a, l, i-1);//递归调用
quickSort(a, i+1, r);//递归调用
}
堆排序
(1) 原理: 堆排序是把数组看作堆,第i个结点的孩子结点为第2i+1和2i+2个结点(不超出数组长度前提下),堆排序的第一步是建堆,然后是取堆顶元素然后调整堆。建堆的过程是自底向上不断调整达成的,这样当调整某个结点时,其左节点和右结点已经是满足条件的,此时如果两个子结点不需要动,则整个子树不需要动,如果调整,则父结点交换到子结点位置,再以此结点继续调整。
(2) 思路:
-
首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
-
将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
-
将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
(3) 代码:
void heapAdjust(int a[], int i, int nLength)
{
int nChild;
int nTemp;
for (nTemp = a[i]; 2 * i + 1 < nLength; i = nChild)
{
// 子结点的位置=2*(父结点位置)+ 1
nChild = 2 * i + 1;
// 得到子结点中较大的结点
if ( nChild < nLength-1 && a[nChild + 1] > a[nChild])
++nChild;
// 如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
if (nTemp < a[nChild])
{
a[i] = a[nChild];
a[nChild]= nTemp;
}
else
// 否则退出循环
break;
}
}
// 堆排序算法
void heap_sort(int a[],int length)
{
int tmp;
// 调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
//length/2-1是第一个非叶节点,此处"/"为整除
for (int i = length / 2 - 1; i >= 0; --i)
heapAdjust(a, i, length);
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (int i = length - 1; i > 0; --i)
{
// 把第一个元素和当前的最后一个元素交换,
// 保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
/// Swap(&a[0], &a[i]);
tmp = a[i];
a[i] = a[0];
a[0] = tmp;
// 不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
heapAdjust(a, 0, i);
}
}
归并排序
(1) 原理:把待排序列看成由两个有序的子序列,然后合并两个子序列,然后把子序列看成由两个有序序列…倒着来看,其实就是先两两合并,然后四四合并…最终形成有序序列。
(2) 思路:只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
(3) 代码:
//将有序数组a[]和b[]合并到c[]中
void MemeryArray(int a[], int n, int b[], int m, int c[])
{
int i, j, k;
i = j = k = 0;
while (i < n && j < m)
{
if (a[i] < b[j])
c[k++] = a[i++];
else
c[k++] = b[j++];
}
while (i < n)
c[k++] = a[i++];
while (j < m)
c[k++] = b[j++];
}
希尔排序
(1) 原理:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。
(2) 思路:在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。
然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。
(3) 代码:
// 希尔排序的一趟插入
public void shellInsert(int[] nums, int d) {
for (int i = d; i < nums.length; i++) {
int j = i - d;
// 记录要插入的数据
int temp = nums[i];
// 从后向前,找到比其小的数的位置
while (j >= 0 && nums[j] > temp) {
// 向后挪动
nums[j + d] = nums[j];
j -= d;
}
// 存在比其小的数
if (j != i - d)
nums[j + d] = temp;
}
}
// 希尔排序
public void shellSort(int[] nums) {
if (nums == null || nums.length == 0)
return;
int d = nums.length / 2;
while (d >= 1) {
shellInsert(nums, d);
d /= 2;
}
}