冒泡排序,每次遍历都把最大元素(也可以是最小元素)放到无序数组的最后面。
冒泡排序是一个稳定的排序算法。
时间复杂度计算:
最优情况下,已经排好序了,只需双层循环,n*(n-1)/2 。是O( n^2 );
最坏情况,元素是逆序,每次遍历都要3次交换, 3n*(n-1)/2。是O( n^2 );
void bubbleSort(std::vector& arr)
{
int len = arr.size();
for (int i = 0; i < len-1; i++)
{
int tmp = 0;
for (int j = i; j < len-1; ++j)
{
if (arr[j] > arr[j+1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j+1] = tmp;
}
}
}
}
选择排序是一个稳定的排序。
步骤:
依次在无序的序列中找到最大或最小的元素,与无序序列的最后元素交换。直到所有元素有序。
时间复杂度:从代码中可以看出一共遍历了n + n-1 + n-2 + … + 2 + 1 = n * (n+1) / 2 = 0.5 * n ^ 2 + 0.5 * n,那么时间复杂度是O(N^2)。
*/
void selectionSort(std::vector& arr)
{
int len = arr.size();
for (int i = 0; i < len - 1; ++i)
{
int minindex = i;
for (int j = i + 1; j < len - 1; ++j)
{
if (arr[minindex] > arr[j])
{
minindex = j;
}
}
//交换
int tmp = arr[minindex];
arr[minindex] = arr[i];
arr[i] = tmp;
}
}
插入排序是一个也是一个稳定的排序算法。
算法分析:插入排序在有序序列基础上,一次插入一个元素。有序序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是插入元素要和已经有序依次比较,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
时间复杂度依然是O(N^2)。
void insertionSort(std::vector&arr)
{
int len = arr.size();
for (int i = 1; i < len; ++i) //可以认为第一个排好序了
{
int j = i - 1; //指向有序的序列尾
int val = arr[i]; //将i取出,因为序列要后移,会覆盖i
while (j >=0 && arr[j] > val)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = val; //j + 1
}
}
希尔排序是第一个时间复杂度突破O(n^2)的排序算法。
是插入排序的改进版,优先比较距离远的元素,是不稳定的排序算法。
思想:希尔排序是把序列按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序时间复杂度的下界是O(n*log2n)。
希尔算法在最坏的情况下和平均情况下执行效率相差不是很多。
void shellSort(std::vector&arr)
{
//分成多个间隔,一般/2
int len = arr.size();
for (int i = len / 2; i > 0; i = i / 2)
{
//当i = 1时,就是整个数组的排序
for (int j = i; j < len; ++j)
{
//这里多个组同时插入排序
int cur = arr[j];
int k = j;
while (k - i >= 0 && arr[k - i] > cur)
{
arr[k] = arr[k - i];
k -= i;
}
arr[k] = cur;
}
}
}
归并排序,是稳定排序
采用了分治法的思想:将大的序列划分小的序列,排序然后合并。
时间复杂度O(nlogn)
void merge(std::vector&arr, int l, int m, int r)
{
int len = r - l + 1;
std::vector tmp(r - l+1, 0);
int left = l;
int right = m + 1;
int i = 0;
while (left <= m && right <= r)
{
if (arr[left] > arr[right])
{
tmp[i] = arr[right];
right++;
}
else
{
tmp[i] = arr[left];
left++;
}
i++;
}
//判断是否完全合并完
if (left > m)
{
while (right <= r)
{
tmp[i] = arr[right];
i++;
right++;
}
}
else if (right >= m)
{
while (left <= m)
{
tmp[i] = arr[left];
i++;
left++;
}
}
//将数据返回
for (int i = 0; i < len; ++i)
{
arr[i+l] = tmp[i];
}
}
void mergeSort(std::vector&arr, int l, int r)
{
int len = r - l + 1;
if (len < 2)
{
return;
}
int mid = (l + r) / 2;
mergeSort(arr, l, mid);
mergeSort(arr, mid+1, r);
merge(arr, l, mid, r);
}
void mergeSort(std::vector&arr)
{
int len = arr.size();
mergeSort(arr, 0, len - 1);
}
是不稳定算法。
快速排序的基本思想:通过一趟排序将待排序列分隔成独立的两部分,其中一部分序列的元素均比另一部分的元素小,则可分别对这两部分序列继续进行排序,以达到整个序列有序。
时间复杂度:O(nlogn)
最坏能达到O(n^2)
int partition(std::vector&arr, int l, int r)
{
int left = l;
int right = r;
int val = arr[l];
while (left < right)
{
while (left=val)
{
right--;
}
if (left < right)
{
arr[left++] = arr[right];
}
while (left < right&&arr[left] &arr, int l, int r)
{
if (l < r)
{
int p = partition(arr, l, r);
if (p != l)
{
quickSort(arr, l, p - 1);
}
if (p != r)
quickSort(arr, p + 1, r);
}
}
void quickSort(std::vector&arr)
{
int len = arr.size();
quickSort(arr, 0, len - 1);
}
参考博客
/*
堆排序
时间复杂度nlogn
不稳定
*/
void adjustHeap(std::vector&arr, int i, int length)
{
//取出当前节点的值
int tmp = arr[i];
for (int k = 2 * i + 1; k < length; k = 2 * k + 1)
{
//从左子节点开始
if (k + 1 < length && arr[k] < arr[k + 1])
{
k++; //移到右节点,主要是找到大的子节点,来判断
}
if (arr[k] > tmp)
{
//子节点大于父节点, 就将值赋给父节点
arr[i] = arr[k];
i = k; //i 指向新的位置
}
else
{
//父节点大于子节点
break;
}
arr[i] = tmp; //将tmp 放入正确的位置
}
}
void buildHeap(std::vector&arr)
{
int len = arr.size();
for (int i = len/2-1; i >= 0; i--)
{
//从第一个非叶子结点开始
adjustHeap(arr, i, arr.size());
}
}
//堆排序
void heapSort(std::vector&arr)
{
//建立堆
buildHeap(arr);
//
for (int i = arr.size() - 1; i > 0; i--)
{
//交换
int tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
adjustHeap(arr, 0, i); //从上向下调整, 这里每次长度减一
}
}