一、冒泡排序
冒泡排序的原理很简单,就是每次比较相邻的两个元素,如果第i+1个元素小于第i个元素交换两个位置,这样在一次循环过程中最大的元素就会在数组的末尾,然后只要进行n-1次循环,就会将数组排序完成;
void bubbleSort(vector<int>& nums){
int n=nums.size();
for (int i=0;i<n-1;i++)
{
for(int j=0;j<n-1-i;j++)
{
if (nums[i]>nums[i+1])
{
int tmp=nums[i];
nums[i]=nums[i+1];
nums[i+1]=tmp;
}
}
}
}
二、选择排序
选择排序也是比较简单的一种排序方法,通过不断比较当前元素与该元素之后的所有元素的大小进行排序,例如当前元素为第i个元素,然后从i+1到最后所有的元素中寻找一个最小的元素,与第i个元素交换。
void selectSort(vector<int>& nums){
int n=nums.size();
for (int i=0;i<n-1;i++)
{
int index=i;
for (int j=i+1;j<n;j++)
{
if (nums[index]>nums[j])
{
index=j;
}
}
int tmp=nums[index];
nums[index]=nums[i];
nums[i]=tmp;
}
}
三、插入排序
插入排序的思想就是打扑克牌的排序思想,比如说我们在打扑克牌的时候,每抓取一张扑克牌都会将抓到的扑克牌放到手中牌的合适的位置,插入排序也是这样,前一个元素因为只有一个,所以认为是有序的,从第二个元素开始,与之前的每一个元素进行比较,如果小于之前的某一个元素就与之交换,直到最后一个元素排序完成。
void insertSort(vector<int>& nums){
int n=nums.size();
for (int i=1;i<n;i++)
{
int j=i;
while (j>=1&&nums[j]<nums[j-1])
{
int tmp=nums[j];
nums[j]=nums[j-1];
nums[j-1]=tmp;
j--;
}
}
}
总结
以上三种排序方法分别为冒泡排序,选择排序以及插入排序,三者有共同点和不同点;
相同点:时间复杂度都为O(n2),空间复杂度都为O(1);
不同点:
- 选择排序是不稳定的,冒泡排序、插入排序是稳定的;
- 在这三个排序算法中,选择排序交换的次数是最少的;
- 在数组几乎有序的情况下,插入排序的时间复杂度接近线性级别。
四、希尔排序
希尔排序实质上是对插入排序的一种优化,通过比较距离一定间隔的两个元素来进行排序,从而将元素放置在更接近最终的位置;具体的做法是首先定义一个gap,将数组分成gap个子序列,然后对这些子序列进行插入排序;
void shellSort(vector<int>& nums){
int n=nums.size();
for (int gap=n/2;gap>=1;gap/=2)
{
for (int curIndex=0;curIndex<gap;curIndex++)
{
for (int i=curIndex+gap;i<n;i+=gap)
{
int j=i;
while(j>=gap&&nums[j]<nums[j-gap])
{
int tmp=nums[j];
nums[j]=nums[j-gap];
nums[j-gap]=tmp;
j-=gap;
}
}
}
}
}
五、堆排序
堆排序的思想就是利用堆这种数据结构,通过构建最大堆或者最小堆来实现堆排序。
- 首先我们要将整个数组看成一个完全二叉树,例如对于任意的i,left=2i+1,right=2i+2;分别对应于i节点的左子节点、右子节点。
- 其次,从右向左开始遍历每一个非叶子结点,例如数组长度为n,则最右边的非叶子节点为n/2-1,然后观察其子结点中的最大值是否大于该值,如果大于该值,就进行交换。此外,对于较左边的非叶子节点/根节点的子节点,可能并非叶子结点,因此,在我们将两个节点进行交换后,还需要考虑交换后的子节点是否还需要调整。
- 当遍历到最左边的时候,最大堆就构建好了,然后从堆顶不断取最大元素与数组中的最后一个元素进行交换,在调整前n-2个元素组成的最大堆;
void heapify(vector<int>& nums,int n,int i){
int largest=i;
int left=2*i+1;
int right=left+1;
if (left<n&&nums[largest]<nums[left])
{
largest=left;
}
if (right<n&&nums[largest]<nums[right])
{
largest=right;
}
if (largest!=i)
{
swap(nums[i],nums[largest]);
heapify(nums,n,largest);
}
}
void heapSort(vector<int>& nums){
int n=nums.size();
//构建最大堆
for (int i=n/2-1;i>=0;i--)
{
heapify(nums,n,i);
}
//取出堆顶元素,与最后一个元素交换
for (int i=n-1;i>=0;i--)
{
swap(nums[i],nums[0]);
heapify(nums,i,0);
}
}
六、快速排序
快速排序的原理主要是利用到了分治的思想,具体的步骤如下所示:
- 从数组中随机选择一个基准(Pivot),一般可以选择第一个、最后一个或者是中间的元素;
- 其次,重新排列元素,使得基准左边的数,都比基准值小,右边的数都比基准值大;
- 然后递归的对左右两边的数进行重排列;
快速排序非常重要的一个部分就是分区函数,这一点可以利用双指针来进行;
int partition(vector<int>& nums,int start,int end){
int left=start+1,right=end;
//选择第一个元素作为基准;
int pivot=nums[start];
while (left<=right){
while (left<=right&&nums[left]<=pivot)
left++;
while (left<=right&&nums[right]>=pivot)
right--;
if(left<right)
{
swap(nums[left],nums[right]);
}
}
swap(nums[start],nums[right]);
return right;
}
void quickSort(vector<int>& nums,int start,int end){
if (start>=end)
return ;
int pi=partition(nums,start,end);
quickSort(nums,start,pi-1);
quickSort(nums,pi+1,end);
}