冒泡排序
基本思想是相邻的两个数比较,逆序则交换,直到没有逆序的记录为止
常见解法
比较n-1趟,每趟都将最大的数放在当前的尾部,然后尾部前移
改进算法
每一趟比较时记录当前是否有元素发生了交换,如果有就继续下一趟的交换,如果没有说明所有的元素都有序了,不用进行下一趟比较了
eg:1,2,3,4,5,6,7,9,8;
第一轮结束后数组为:1,2,3,4,5,6,7,8,9
第二轮比较的时候发现:这一轮没有数据交换,所有的数据都有序
这里看上去第二轮也有点多余,那么此时将flag作为一轮比较后的逆序数,当逆序数小于等于1的时候就可以不用继续比较了,因为在这一轮比较中,已经把数组中唯一的一个逆序对变成了有序,数组中所有的元素都应该有序了
时间复杂度为O(n^2),空间复杂度为O(n);稳定,每一轮有多少逆序交换多少次
//冒泡排序
void bubbleSort(vector<int>& vec)
{
//int flag = vec.size()
bool flag = true; //记录当前这趟比较是否有数据交换
//for (int i = 0; i < vec.size() - 1 && flag <= 1; i++) 逆序数小于等于1 说明有序
for (int i = 0; i < vec.size() - 1 && flag; i++) //当某一趟走完发现没有交换任何元素,说明所有元素都有序,没必要继续判断
{
//flag = 0 开始逆序为0
flag = false; //开始交换时标志为false
for (int j = 1; j < vec.size() - i; j++)
{
if (vec[j] < vec[j - 1])
{
swap(vec[j], vec[j - 1]);
//flag++; 存在逆序就 +1
flag = true; //说明这趟存在逆序的元素即还要继续走下一趟
}
}
}
}
选择排序
基本思想是 每一轮比较记录当前最大数的下标,然后与当前尾部元素交换,尾部前移(也可以记录最小数的下标与当前的首位交换,首部后移)
时间复杂度为O(n^2),空间复杂度为O(n);不稳定,每一轮最多只用交换一次,效率略优于冒泡
//选择排序
void selectSort(vector<int>& vec)
{
int maxIndex,j;
for (int i = 0; i < vec.size() - 1; i++)
{
maxIndex = 0; //初始化最大数的索引
for (j = 1; j < vec.size() - i; j++) //找到这一轮的最大数索引
{
if (vec[j] > vec[maxIndex])
maxIndex = j;
}
if(maxIndex != j - 1)
swap(vec[maxIndex], vec[j - 1]); //将最大数与数组尾部元素交换
}
}
插入排序
基本思想是 将当前元素插入到前面有序的数组中,然后有序记录 + 1
时间复杂度为O(n^2),空间复杂度为O(n);稳定,效率略优于冒泡和选择排序
void insertSort(vector<int>& vec)
{
int tmp, j;
for (int i = 1; i < vec.size(); i++)
{
if (vec[i] < vec[i - 1])
{
tmp = vec[i]; //记录当前要插入的元素
for (j = i - 1; j >= 0; j--)
{
if (vec[j] < tmp) //找到插入的位置
{
vec[j + 1] = tmp;
break;
}
vec[j+1] = vec[j];
}
if (j < 0) //插入到首位
vec[0] = tmp;
}
}
}
希尔排序
基本思想是 将所有元素根据当前的gap大小分组,将每一组插入排序,然后gap - 1,继续对每组进行插入排序,直到gap < 1
时间复杂度为O(n^3/2),空间复杂度为O(n);跳跃式交换,不稳定,效率优于冒泡和选择和直接插入排序
//希尔
void shellSort(vector<int>& vec)
{
int len = vec.size();
int gap = len / 3 + 1;
while (gap > 0)
{
for (int i = 0; i < gap; i++)
{
int tmp, k;
for (int j = i + gap; j < len; j += gap) //每间隔gap的元素为一组,对当前组进行插入排序
{
if (vec[j] < vec[j - gap])
{
tmp = vec[j];
for (k = j - gap; k >= i; k -= gap)
{
if (vec[k] < tmp) //插入位置在当前组中间
{
vec[k + gap] = tmp;
break;
}
vec[k + gap] = vec[k];
}
if (k < i) //插入位置在当前组的首位
vec[i] = tmp;
}
}
}
gap--;
}
}
堆排序
基本思想是 将数组想象成一个完全二叉树的数组存储结构,每次将最大的元素调整到根节点,然后将根节点与数组尾部元素交换,尾部前移,再次调整堆进行交换直到尾部前移到首部
时间复杂度为O(nlogn),空间复杂度为O(n);跳跃式交换,不稳定,效率优于冒泡,选择,直接插入排序,希尔
void adjustHeap(vector<int>& vec, int root, int end)
{
int left = root * 2 + 1;
int tmp = vec[root];
while(left < end)
{
int right = left + 1; //当前根节点的右孩子
if(right < end && vec[right] > vec[left]) //右孩子存在且大于左孩子
left++;
if(tmp > vec[left])
break;
vec[root] = vec[left]; //当前的根节点值小于孩子节点中的最大值则交换
root = left; //更新当前的根节点为left,继续往下调整
left = root * 2 + 1;
}
vec[root] = tmp; //更新当前根节点的值
}
void heapSort(vector<int>& vec)
{
//构建大顶堆
for(int i = vec.size() / 2 - 1; i >=0; i--)
adjustHeap(vec,i,vec.size());
//调整n-1次,每次从顶部拿走一个元素
for(int i = vec.size() - 1; i > 0; i--)
{
swap(vec[i], vec[0]);
adjustHeap(vec, 0, i);
}
}
归并排序
基本思想是 将数组不断二分,直到数组中只有一个元素开始合并成一个有序数组tmp,然后将tmp中的数复制到原数组中
时间复杂度为O(nlogn),空间复杂度为O(n),额外申请了一个临时数组,稳定
void merge(vector<int>& vec, vector<int>& tmp, int left, int mid, int right)
{
int i = left;
int j = mid;
int k = 0;
//将两个有序的数组合并到临时数组tmp中
while(i < mid && j < right)
{
if(vec[i] < vec[j])
tmp[k++] = vec[i++];
else
tmp[k++] = vec[j++];
}
//将原数组中未合并的数添加到tmp后面
while(i != mid)
tmp[k++] = vec[i++];
while(j != right)
tmp[k++] = vec[j++];
//将合并的有序数组复制到原数组
k = 0;
while(left < right)
vec[left++] = tmp[k++];
}
void mergeSort(vector<int>& vec, vector<int>& tmp, int left, int right)
{
//前闭后开区间,当数组中只有一个数时返回
if(left + 1 == right)
return;
int mid = left + (right - left) / 2;
mergeSort(vec, tmp, left, mid);
mergeSort(vec, tmp, mid, right);
merge(vec, tmp, left, mid, right);
}
快速排序
基本思想是 将数组根据当前的基准数进行二分,小的在左侧,大的在右侧,然后对左右侧继续二分直到数组中只有一个元素
时间复杂度为O(nlogn),空间复杂度为O(n),不稳定
改进:
选择数组中的三个数取中位数作为基准元素,可以尽可能让二分后的左右两侧元素数量差不多
int partition(vector<int>& vec, int low, int high)
{
int pivotkey = vec[low];
while(low < high)
{
while(low < high && vec[high] >= pivotkey) high--;
vec[low] = vec[high];
while(high < low && vec[low] <= pivotkey) low++;
vec[high] = vec[low];
}
vec[low] = pivotkey;
return low;
}
void quickSort(vector<int>& vec, int left, int right)
{
if(left == right)
return ;
int mid = partition(vec, left, right);
partition(vec, left, mid + 1);
partition(vec, mid - 1, right);
}