排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
希尔排序 | O(nlogn) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n2) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n2) | O(nlogn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
-
冒泡排序
冒泡排序正如其名,这个算法像是泡泡一样往上升,具体步骤如下:
1.将第一个元素与第二元素作比较,如果前者大于后者则交换。按照这个规则从第一个元素一直遍历到最后一对元素比较完为止。
2.从第二个元素开始按上面规则往后遍历,然后作比较进行交换,每次都至少能将一个值放到后面排好序。
3.不断的重复上述过程,直到没有元素交换为止。
class Solution {
public:
//冒泡排序
void BubbleSort(vector<int>& a)
{
for(int i=0;i<a.size()-1;i++)
for(int j=1;j<a.size()-i;j++)
if(a[j-1]>a[j])
swap(a[j-1],a[j]);
}
vector<int> MySort(vector<int>& arr) {
BubbleSort(arr);
return arr;
}
};
-
选择排序
选择排序就是我每一趟排序都找到一个值,放在我当前的所在位置,具体步骤如下:
-
首先从下标为 0 开始,遍历一遍数组并找到最小的值,把它放到下标为 0 的位置。
-
然后从下标为 1 开始,遍历后续数组并找到后面元素中最小的值,把它放到下标为 1 的位置。
-
以此类推,直到最后一趟排序结束。
class Solution {
public:
//直接选择排序
void SelectSort(vector<int>& a)
{
for(int i=0;i<a.size();i++)
{
int min=i;
for(int j=i;j<a.size();j++)
if(a[j]<a[min])
min=j;
swap(a[i],a[min]);
}
}
vector<int> MySort(vector<int>& arr) {
SelectSort(arr);
return arr;
}
};
-
插入排序
插入排序的每趟排序都会将当前的元素与其前面的元素进行排序,步骤如下:
1.从第一个元素开始,因为第一个元素之前没有元素,所以直接认为它是有序的了。
2.找到下一个元素并拿出来,对前面已经排好序的元素进行从后往前的遍历,如果遍历的元素要大于拿出来的元素,则往后移一位。直到遍历的元素小于取出元素或者已经遍历到最开头,遍历结束并将拿出来的元素插入该位置之后。
3.重复上述步骤,直至最后一趟排序结束。
class Solution {
public:
//插入排序
void InsertSort(vector<int>& a)
{
for(int i=1;i<a.size();i++)
{
if(a[i]<a[i-1])
{
int tmp=a[i];
int j=i;
while(tmp<a[j-1])
{
a[j]=a[j-1];
j--;
}
a[j]=tmp;
}
}
}
vector<int> MySort(vector<int>& arr) {
InsertSort(arr);
return arr;
}
};
-
希尔排序
希尔排序是插入排序的一种更高效的改进版本,算法步骤如下:
-
选择一个间隔距离 k ,对元素间隔距离为 k 的序列进行排序,如果存在逆序则进行交换操作。
-
缩小 k ,再次进行排序操作。
-
重复上述操作,直至 k 小于 1 时停止排序。
class Solution {
public:
//Shell排序
void ShellSort(vector<int>& a)
{
int h=1;
while(h<a.size()/3)
h=3*h+1;
while(h>=1)
{
for(int i=h;i<a.size();i++)
for(int j=i;j>=h&&a[j]<a[j-h];j-=h)
swap(a[j],a[j-h]);
h/=3;
}
}
vector<int> MySort(vector<int>& arr) {
ShellSort(arr);
return arr;
}
};
-
归并排序
归并排序采用了分治法的思想,不断将两个有序的序列进行合并从而最终得到整个有序的序列,算法步骤如下:
1、把当前长度为 n 的序列分为两个长度为 n / 2 的子序列,对子序列进行操作。
2、同样对两个子序列进行划分,再分别对其子序列进行划分操作,直至子序列中只有一个元素为止就不再进行划分,而是进行回溯。
3、通过回溯的子序列进行排序操作,我们额外创建一个数组出来,将回溯的两个子序列进行合并操作。
4、一直往上回溯,这样每次得到的两个回溯的子序列都是有序的,可以方便我们进行合并操作,合并的序列也会是有序的。
class Solution {
public:
//归并排序
void Merge(vector<int>& a,int l,int r)
{
if(l>=r)
return;
int m=(l+r)/2;
Merge(a,l,m);
Merge(a,m+1,r);
MergeSort(a,l,m,r);
}
void MergeSort(vector<int>& a,int l,int m,int r)
{
vector<int>temp;
int left=l,right=m+1;
while(left<=m&&right<=r)
temp.emplace_back(a[left]<=a[right]?a[left++]:a[right++]);
while(left<=m)
temp.emplace_back(a[left++]);
while(right<=r)
temp.emplace_back(a[right++]);
for(int j=0;j<temp.size();j++)
a[l+j]=temp[j];
}
vector<int> MySort(vector<int>& arr) {
Merge(arr,0,arr.size()-1);
return arr;
}
};
-
快速排序
快速排序也是利用了递归思想,算法步骤如下:
-
选取第一个的数作为基准数。
-
将小于基准数的元素换到前面,将大于基准数的元素换到后面。
-
重复上述操作,直到区间只有一个数为止。
class Solution {
public:
//快速排序
void QuickSort(vector<int>& a,int l,int r)
{
if(l>=r) return;
int i=l,j=r,temp=a[i];
while(i<j)
{
while(i<j&&a[j]>=temp)
j--;
if(i<j)
{
a[i]=a[j];
i++;
}
while(i<j&&a[i]<temp)
i++;
if(i<j)
{
a[j]=a[i];
j--;
}
}
a[i]=temp;
QuickSort(a,l,i-1);
QuickSort(a,i+1,r);
}
vector<int> MySort(vector<int>& arr) {
QuickSort(arr,0,arr.size()-1);
return arr;
}
};
-
堆排序
1、先将初始化的数组序列构建成大顶堆。
2、将堆顶元素和最后一个元素进行交换,此时最后一个元素的位置就是该序列的最大值了。然后再对其前面的所有元素进行堆更新,即从堆顶元素往下进行堆操作,但是这里已经排好序的最后一个元素不参与对操作。
3、重复上述步骤,每次都从堆顶取剩余元素的最大值放到堆即剩余元素的最后一个,然后从堆顶往下进行对操作,直至所有元素都已经操作完。
class Solution {
public:
//堆排序
void MaxHeapify(vector<int>& a,int start,int end)
{
int dad=start;
int son=dad*2+1;
while(son<=end)
{
if(son+1<=end&&a[son+1]>a[son])
son++;
if(a[dad]>a[son])
return;
else
{
swap(a[dad],a[son]);
dad=son;
son=dad*2+1;
}
}
}
void HeapSort(vector<int>& a)
{
for(int i=a.size()/2-1;i>=0;i--)
MaxHeapify(a,i,a.size()-1);
for(int i=a.size()-1;i>0;i--)
{
swap(a[0],a[i]);
MaxHeapify(a,0,i-1);
}
}
vector<int> MySort(vector<int>& arr) {
HeapSort(arr);
return arr;
}
};
-
二分查找法
1.确定搜索范围:初始化两个指针,left指向数组的起始位置,right指向数组的结束位置。
2.计算中间位置:计算中间位置的索引 mid,通常使用公式 mid = left + (right - left) / 2。
3.比较中间元素:比较数组中 mid 位置的元素与目标值 target。
如果 array[mid] 等于 target,则查找成功,返回 mid。
如果 array[mid] 小于 target,则目标值在 mid 的右侧,更新 left 为 mid + 1。
如果 array[mid] 大于 target,则目标值在 mid 的左侧,更新 right 为 mid - 1。
4.重复步骤:重复步骤2和3,直到 left 大于 right,此时搜索范围为空,表示目标值不存在于数组中。
5.返回结果:如果找到目标值,返回其索引;否则,返回一个表示未找到的特殊值(如 -1)。
// 二分查找模板函数
template <typename T>
int binarySearch(const T& container, const typename T::value_type& target) {
int left = 0;
int right = container.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (container[mid] == target) {
return mid; // 找到目标值,返回索引
} else if (container[mid] < target) {
left = mid + 1; // 目标值在右侧
} else {
right = mid - 1; // 目标值在左侧
}
}
return -1; // 未找到目标值
}