插入排序
直接插入排序
插入排序:每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,直到全部插入完成。
原始:89 45 54 29 90
第1个数肯定是有序的。
i=1,89 45 54 29 90,j从0开始与i比较,a[j]>a[i],则交换,即 45 89 54 29 90;
i=2,45 89 54 29 90,j=0,a[j]不大于a[i]
j=1,a[j]>a[i],则交换,45 54 89 29 90;
i=3,45 54 89 29 90,j=0,a[j]>a[i],则交换,29 54 89 45 90,
j=1,a[j]>a[i],则交换,29 45 89 54 90,
j=2,a[j]>a[i],则交换,29 45 54 89 90,
…………………..
当数据正序时,执行效率最好,每次插入都不用移动前面的元素,时间复杂度为O(N)。
当数据反序时,执行效率最差,每次插入都要前面的元素后移,时间复杂度为O(N^2)。
所以,数据越接近正序,直接插入排序的算法性能越好。
直接插入排序的过程中,不需要改变相等数值元素的位置,所以它是稳定的算法。
代码如下:
void InsertSort(int a[], int n)
{
int i, j, tmp;
//第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列
for(i=1; i<n; i++)
{
tmp = a[i];
for(j=i-1; j>=0 && a[j] > tmp; j--)
{
a[j+1] = a[j];
}
a[j+1] = tmp;
}
}
折半插入排入(二分插入排序)
折半插入排序:顺序地把待排序的序列中的各个元素按其关键字的大小,通过折半查找插入到已排序的序列的适当位置。
代码如下:
void BinaryInsertSort(int a[], int n)
{
int i, j, low, high, mid, tmp;
for(i=1; i<n; i++)
{
low = 0;
high = i - 1;
tmp = a[i];
while(low <= high)
{
mid = (low+high) / 2;
if(a[mid] > a[i])
high = mid - 1;
else
low = mid + 1;
}
for(j=i-1; j>=low; j--)
a[j+1] = a[j];
a[low] = tmp;
}
}
希尔排序
希尔排序的一般步骤为:
1.先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中,在各组内进行直接插人排序。
2.取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
希尔排序的时间复杂度与增量的选取有关,一般认为其平均时间复杂度为
希尔排序是不稳定的算法。对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化。
希尔排序的速度通常比直接插入排序快。
代码如下:
void ShellSort(int a[], int n)
{
int i, j, tmp;
int d = n/2;
while(d > 0)
{
for(i=d; i<n; i++)
{
tmp = a[i];
for(j=i; j>=d && a[j-d] > tmp; j=j-d)
{
a[j] = a[j-d];
}
a[j] = tmp;
}
d = d/2;
}
}
交换排序
冒泡排序
冒泡排序在扫描过程中两两比较相邻记录,如果反序则交换,最终,最大记录就被“沉到”了序列的最后一个位置,第二遍扫描将第二大记录“沉到”了倒数第二个位置,重复上述操作,直到n-1 遍扫描后,整个序列就排好序了。
时间复杂度与初始序列有关,当数据正序时,时间复杂度为O(N)。
当数据反序时,时间复杂度为O(N^2)。因而,平均时间复杂度为O(N^2)。
当i>j且a[i]==a[j]时,两者没有逆序,不进行交换,所以冒泡排序是一种稳定的排序算法。
代码如下:
void BubbleSort(int a[], int n)
{
int i, j;
bool exchange;
for(i=0; i<n; i++)
{
exchange = false;
for(j=n-1; j>i; j--)
{
if(a[j-1] > a[j])
{
swap(&a[j], &a[j-1]);
exchange = true;
}
}
if(!exchange)
break;
}
}
快速排序
该方法的基本思想是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
https://blog.csdn.net/morewindows/article/details/6684558
代码如下:
int Partition(int a[], int left, int right)
{
//随机选取基准
int pos = (rand() % (left - right + 1) + left);
swap(&a[pos], &a[left]);
int X = a[left];
int i = left, j = right;
while(i < j)
{
while(i < j && a[j] >= X)
j--;
a[i] = a[j];
while(i < j && a[i] <= X)
i++;
a[j] = a[i];
}
a[i] = X;
return i;
}
//快速排序
void quickSort(int a[], int left, int right)
{
int t = 0;
if(left < right)
{
t = Partition(a, left, right);
quickSort(a, left, t-1);
quickSort(a, t+1, right);
}
}
选择排序
简单选择排序
初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。
选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
放到已排序序列的末尾(即交换),该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法。时间复杂度为O(N^2)。
代码如下:
void SelectSort(int a[], int n)
{
int min = 0;
for(int i=0; i<n-1; i++)
{
min = i;
for(int j=i+1; j<n; j++)
{
if(a[min] > a[j])
min = j;
}
if(min != i)
swap(&a[min], &a[i]);
}
}
堆排序
每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
其基本思想为(大顶堆):
1、将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区
2、将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn)
3、由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
https://www.cnblogs.com/chengxiao/p/6129630.html
https://www.cnblogs.com/skywang12345/p/3602162.html
代码如下:
void HeapSort(int a[], int n)
{
//1.构建大顶堆
//从第一个非叶子结点开始从下至上,从右至左调整结构
for(int i=n/2-1; i>=0; i--)
{
heapAdjust(a, i, n);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int i=n-1; i>=0; i--)
{
swap(a[i], a[0]);
heapAdjust(a, 0, i);
}
}
//堆的向下调整算法
/*
数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
其中,N为数组下标索引值,如数组中第1个数对应的N为0。
*/
void heapAdjust(int a[], int i, int len)
{
int tmp = a[i]; //i为当前节点的编号
for(int k=2*i+1; k<len; k=2*k+1)
{
//i节点的左子结点小于右子结点,k指向右子结点
if((k+1 < len) && (a[k] < a[k+1]))
{
k++;
}
//子节点大于父节点,将子节点值赋给父节点
if(tmp < a[k])
{
a[i] = a[k];
i = k;
}
else
break;
}
//将temp值放到最终的位置
a[i] = tmp;
}
归并排序
归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(N*lgN)。
归并排序是稳定的算法。
1、从上往下
从上往下的归并排序,基本包括3步:
① 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
② 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。
③ 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。
//从上往下的归并排序
void Merge(int a[], int left, int mid, int right)
{
int len = right-left+1;
int i = left, j = mid + 1, k = 0;
int *tmp = (int *)malloc(sizeof(int)*len);
while(i <= mid && j <= right)
{
if(a[i] < a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while(i <= mid)//复制第一段余下的部分
{
tmp[k++] = a[i++];
}
while(j <= right)//复制第二段余下的部分
{
tmp[k++] = a[j++];
}
for(i=0; i<k; i++)
{
a[i+left] = tmp[i];
}
free(tmp);
}
void merge_sort_uptodown(int a[], int start, int end)
{
if(a == NULL || start>=end)
return;
int mid = (start+end) / 2;
merge_sort_uptodown(a, start, mid);
merge_sort_uptodown(a, mid+1, end);
// a[start...mid] 和 a[mid...end]是两个有序空间,将它们排序成一个有序空间a[start...end]
Merge(a, start, mid, end);
}
2、从下往上
通过”从下往上的归并排序”来对数组{80,30,60,40,20,10,50,70}进行排序时:
1. 将数组{80,30,60,40,20,10,50,70}看作由8个有序的子数组{80},{30},{60},{40},{20},{10},{50}和{70}组成。
2. 将这8个有序的子数列两两合并。得到4个有序的子树列{30,80},{40,60},{10,20}和{50,70}。
3. 将这4个有序的子数列两两合并。得到2个有序的子树列{30,40,60,80}和{10,20,50,70}。
4. 将这2个有序的子数列两两合并。得到1个有序的子树列{10,20,30,40,50,60,70,80}。
代码如下:
void Merge_group(int a[], int gap, int n)
{
int i = 0;
for(i=0; i+2*gap-1<n; i=i+2*gap)
{
Merge(a, i, i+gap-1, i+2*gap-1);
}
if(i+gap-1 < n-1)
Merge(a, i, i+gap-1, n-1);
}
void merge_sort_down2up(int a[], int n)
{
if(a == NULL || n <= 0)
return;
int gap;
for(gap=1; gap<n; gap*=2)
{
Merge_group(a, gap, n);
}
}