冒泡排序
基本思想:为什么叫冒泡排序呢,,是因为每一次排序就像是鱼儿吐泡泡,一个个泡泡从水底到水面会变得越来越大,泡泡就像元素,随着算法的进行,较大的元素就会到后面去。如果升序排列,那么就会对相邻两个元素进行比较,如果前者大于后者,那么就交换这了两个元素,直到最大的元素被排到最后一个位置,第一趟冒泡完成,进行第二趟冒泡,把次大的元素排到倒数第二的位置,重复这个过程。直到所有的元素都被冒泡到合适的位置,停止。
具体步骤:
代码实现:
template<class T>//仿函数,本质是函数对象,对()进行重载,功能类似于函数
struct Less
{
bool operator ()(const T& left,const T&right)
{
return left < right;
}
};
template<class T>
struct Greater
{
bool operator ()(const T& left, const T&right)
{
return left > right;
}
};
template < class T,class compare=Less<T>>//使用模板参数,处理不同类型数据,以及指定排序方式,这里默认降序
void bubblesort(T* array,int sz)
{
bool IsChanged = false;//
for (int i = 0; i < sz - 1; i++)
{
bool IsChanged = false;
for (int j = 0; j < sz - 1 - i; j++)
{
if (compare()(array[j], array[j + 1]))//仿函数
swap(array[j], array[j + 1]);
IsChanged = true;
}
if (IsChanged == false)//如果一次都没交换,结束循环,提高程序效率
break;
}
}
算法性能
时间复杂度:平均时间复杂度O(N^2)最好和最坏情况下。
空间复杂度:O(1)
稳定性:稳定
快速排序
基本思想:首先,在待排序列中选择一个key值,然后将其他元素以此和key值比较,比key值小的放到key的前面,比key大的放到key的后面,这样就吧序列分成两个区间,然后递归起左右区间,直到区间只剩一个元素的时候,那么停止递归,此时可以认为子区间是已经有序,然后一层一层的往上返回,结束后,整个区间就已经排好序。具体看下图。
具体步骤:
- 首先,在待排序的区间选出来一个基准值key.
- 用key依次跟去他元素比较,把比key小的元素放到key的左边,比key大的放到key的右边,此时key就像是一个分水岭,将数据分成两部分。
- 递归排序key的左右区间,重复上述1,2步骤。
- 直到区间元素只剩一下的时候,停止递归。
实现方法
我目前了解的实现快排的方法有三种,分别是左右指针法,挖坑法,和前后指针法。
1.左右指针法
实现步骤:
- 定义两个指针left和right分别指向待排序区间的第一个元素和最后一个元素,这里我们选择left指向的元素为key,那么左指针就需要先走。
- 左指针向右走,当寻找到比key大的元素就停下,右指针向左走,找到比key小的元素就停下来。
- 如果两个指针没有相遇,就交换左右指针指向的元素。
- 直到两个指针相遇就结束,然后把左指针指向的位置赋值为key。
思想流程图:
实现代码
int PartSort1(int *array, int left,int right)//区间左毕右毕
{
int key = right;
while (left < right)
{
//左指针寻找比key大的元素
while (left < right&&array[left] < key)
{
left++;
}
while(left<right&&array[right]>key)//右指针寻找比key小的元素
{
right--;
}
if (array[left] != array[right])
{
swap(array[left], array[right]);
}
}
array[left] = key;
return left;
}
2.挖坑法
具体步骤:
- 首先用left和right两个指针标记待排序列第一个和最后一个元素,这里选择key为right指向的元素,并且第一个坑设置在key的位置.
- 让left向右寻找比key大的元素,找到了就停下来,把这个元素放到坑的位置,把left指向的位置设为新的坑。
- 接下来right向左走,找比key小的元素,找到了就把这个元素填入坑中,并把right现在的位置设为新的坑。
- 重复上述2,3步骤,直到left和right相遇停止,然后最后把坑的位置设置成key.
思想流程图:
实现代码:
int PartSort2(int *array, int left, int right)//挖坑法
{
int key = right;
int keng = right;
while (left < right)
{
while (left < right&&array[left] < key)
{
left++;
}
array[keng] = array[left];
keng = left;
while (left<right&&array[right]>key)
{
right--;
}
array[keng] = array[right];
keng = right;
}
array[keng] = key;
return keng;
}
3.前后指针法
具体步骤:
- 先让pcur指向第一个元素,prev指向pcur的前一个元素,此时选取最后一个元素为key。
- pcur向后走找到比key小的元素就停止,++prev,当prev和pcur不相等的时候,交换两个位置的元素,相等不交换。
- pcur一直往后走,重复2过程,当走到右边界的时候,++prev,并且交换prev和pcur位置的元素。
思想流程图:
代码实现:
int PartSort(int *array, int left, int right)
{
assert(array);
int key = right;
int pcur = left;
int prev = left - 1;
while (pcur<right)
{
if (array[++prev] !=array[ pcur]&&array[pcur]<key)
{
swap(array[prev], array[pcur]);
}
}
++prev;
swap(array[prev], array[pcur]);
}
快排的性能
时间复杂度:快排是一种已知道的最快的排序算法,它的平均时间复杂度为O(NlogN),但是它也是一种不稳定的算法,当它的基准值选取的不合理的时候,那么就会造成O(N^2)的时间复杂度
空间复杂度:O(logN)
稳定性:不稳定
所以说,选择合理的基准值key,影响着快排的效率,上述的基准值都是取的是最后一个元素,下面我们将算法进行改进,通过几种合理的取数法,使得快排左右区间尽可能的分配均匀。
1.三数取中法
基本思想:我们为了把区间尽可能的分配均匀,在取基准值的时候,选取数组最左边,最中间,最右边的元素三个元素中中间大小的元素作为基准值。
int GetMidIndex(int *array, int left, int right)
{
int mid = left + ((right - left) >> 1);
if (array[left] < array[right])
{
if (array[mid] < array[left])
return left;
else if (array[mid] > array[right])
return right;
else
return mid;
}
else
{
if (array[mid] > array[left])
return left;
else if (array[mid] < array[right])
return right;
else
return mid;
}
}
2.小区间优化
基本思想:当区间较小时候,一般认为有13个元素以下,这个时候,快速排序就没有直接插入的性能好了,因为当区间比较小的时候,区间划分的就比较多,快排就像一颗二叉树一样,每一次递归都相当于增加一层高度,当区间比较小的时候,就会快速增加二叉树高度,降低了效率,因此我们在划分到小区间的时候就改为插入排序。
代码实现:
void InsertSort(int *array,int size)
{
for (int i=1;i<size;i++)
{
int end = array[i];
int j = i - 1;
while (j >= 0 && array[j] > end)
{
array[j + 1] = array[j];
j--;
}
array[j + 1] = end;
}
3.非递归实现
基本思想:我们前面讲的快排都是基于递归子区间来实现的,而当数据元素比较多的时候,难免递归的次数会非常多,而每次函数的递归都是一个函数的栈帧过程,十分消耗时间,因此可以借助栈这种方式,来实现递归转非递归。
代码实现:
void QuickSortNoeR(int*array, int left, int right)
{
stack<int> s;
s.push(left);
s.push(right);
while (!s.empty())
{
int start = s.top();
s.pop();
int finish = s.top();
s.pop();
int div = PartSort1(array, start, finish);
if (start < div - 1)
{
s.push(div - 1);
s.push(start);
}
if (finish > div + 1)
{
s.push(finish);
s.push(div + 1);
}
}
}
这就是对于交换排序,本人的学习之谈。