前言:
该文章用虽然用C++实现,但是基本逻辑都一样
//c++类
#include<stack>
class Sort
{
public:
int _size;
Sort();
~Sort();
void Push();
void insert_sort();//插入
void shell_sort();希尔
//
void swap(int* a, int* b);//交换
//
void q_sort(int left, int right);
void q_sort_pointer(int left, int right);//前后指针
void q_sort_non_recursive(int left, int right);//非递归
//
void bubble_sort();//冒泡
//
void merge_sort();//归并
void merge_non_sort();//非递归
//
void count_sort();//计数
void Print();
private:
void _merge_sort(int* temp, int begin, int end);
int _point(int left, int right);
int* _data;
};
//_size是数组大小,_data是存放数据的数组
冒泡排序
这个gif图片很容易可以看出来,冒泡排序每一趟排序就是把最大的或者最小的排出来,放到最后面或者最前面(这个取决于你的循环从那边开始)
我们可以先把一趟循环代码写出来
for (int i = 0; i < _size-1; i++)
{
if (_data[i] < _data[i + 1])
{
swap(_data[i], _data[i + 1]);
}
}
但是我们要清楚,每一趟循环后,数据最后一个总是我们已经排序好的数据(最大或最小),所以,每一趟排序就会减少一次
for (int i = 0; i < _size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (_data[j] > _data[j + 1]) {
swap(_data[j], _data[j + 1]);
}
}
}
插入排序
我们观察gif图中,插入排序有个数据移动的过程,我们可以这样模拟实现,将移动的数据往后赋值一位,在图中表示空出来的位置就是数据已经保存的位置,我们可以插入的位置,同样,我们可以先实现一趟的排序过程
int temp=_data[i];
int end=i-1;
while (end > 0)
{
if (_data[end] > temp)
{
_data[end + 1] = _data[end];//往后赋值,就类似图中的移动,其实就是保存数据
end--;
}
else
{
_data[end] = temp;
break;
}
}
用while循环直到找到我们可以插入的位置,在外层嵌套一个for循环遍历整个数组即可
void Sort::insert_sort()
{
Print();
for (int i = 1; i < _size; i++)
{
int temp = _data[i];
int end = i - 1;
/*while (end > 0)
{
if (_data[end] > temp)
{
_data[end + 1] = _data[end];
end--;
}
else
{
_data[end] = temp;
break;
}
}*/
while (end >= 0 && _data[end] > temp)
{
_data[end + 1] = _data[end];
end--;
}
_data[end + 1] = temp;
}
}
他对比冒泡排序就是在排序过程中,很少交换
希尔排序
希尔排序是一种改进的插入排序算法。它的基本思想是将待排序数组按一定间隔分组,然后对每个分组进行插入排序。随着排序的进行,逐步减小间隔,直至间隔为1,最后对整个数组进行一次插入排序。通过这种方式,希尔排序能够在初始阶段就让数据项跳跃式移动,从而加快排序的速度。而对于这个间隔的开始,有很多说法,现在最常见的开始间隔 gal就是_size/3+1 这是由计算机科学家 Donald Knuth 提出
希尔排序在插入排序的基础上,用长度为gal的段落,将整个数组分成几组,每次排序将每组的第一个数据进行插入排序,将整个数组逐渐趋近有序,并每次插入后改变gal大小,直到gal=1,这时候看看希尔排序的代码就跟插入排序的代码一样了,但是希尔排序的时间复杂度量级是N^1.3,远小于N^2(插入排序)(这个计算就需要学习数学的同学了)
void Sort::shell_sort()
{
int gal = _size;
while (gal>1)
{
gal = gal / 3 + 1;//加一是为了保证最后一次循环 gal是 1
for (int i = gal; i < _size; i++)//插入排序
{
int temp = _data[i];
int end = i - gal;
while (end >= 0 && _data[end] > temp)
{
_data[end + gal] = _data[end];
end-=gal;
}
_data[end + gal] = temp;
}
}
}
qsort
gif图片中,黄色的代表比较对象,也就是基准,快速排序的思想,就是将该数组中所有大于基准的数据放到右边/左边(呃,也就是降序,升序),也就是说,我们需要两个代表左右两边可以交换的位置的指针,知道了这些,我们就可以将每一趟交换的排序写出来
void Sort::_point(int left, int right)
{
int keyi = left;//直接定义,也可以使用三数取中,甚至随机数,但要控制随机数在你数组大小之间
while (left < right)
{
while (left < right && _data[right] >= _data[keyi])
{
--right;
}
//当我们定义左边为kiyi(基准)的时候,一定要从最右边开始比较,防止漏掉左边第一个
while (left < right && _data[left] <= _data[keyi])
{
++left;
}
swap(&_data[left], &_data[right]);
}
swap(&_data[left], &_data[keyi]);
}
对于基准的选择,有很多种,常用的是,左边第一个,取左边,右边,中间的那个中间值和随机值,这些是对排序的优化对于特殊情况了(降序的数组但是我们对他将进行升序)中间的while循环就是为了要找到需要交换的位置,也就是右边大于基准的值,左边小于基准的值,然后交换,第一次交换结束,我们需要将排序好的两段数组再次排序,这就需要我们计算得出,需要再次排序的数组两边的值,所以,我们每一趟排序的后,需要返回两段数组的基准位置,用于计算下次排序的两端的位置,然后重复循环
int Sort::_point(int left, int right)
{
int keyi = left;
while (left < right)
{
while (left < right && _data[right] >= _data[keyi])
{
--right;
}
while (left < right && _data[left] <= _data[keyi])
{
++left;
}
swap(&_data[left], &_data[right]);
}
swap(&_data[left], &_data[keyi]);
return left;
}
void Sort::q_sort(int left, int right)
{
if (right > left)
{
int point = _point(left, right);
q_sort(left, point - 1);
q_sort(point + 1, right);
}
}
非递归
对于非递归的快速排序的写法,需要用到数据结构,(栈),我可以先把代码贴出来,学到栈的自然能看懂,这里我是一个数据一个数据进行的入栈,也可以使用数组入栈
void Sort::q_sort_non_recursive(int left, int right)
{
stack<int> sk;
sk.push(right);
sk.push(left);
while (!sk.empty())
{
left = sk.top();
sk.pop();
right = sk.top();
sk.pop();
int point = _point(left, right);
if (point + 1 < right)
{
sk.push(right);
sk.push(point + 1);
}
if (left < point - 1)
{
sk.push(point - 1);
sk.push(left);
}
}
}
归并排序
这张gif图就并不是很直观,他是按照非递归的方法,进行的排序。归并排序的主要是控制每次归并的两个数组的边界,保证不越界和不少元素也就是将两个有序数组进行排序,而对于一个无序的数组,他有序的地方在哪,一个数组中,就一个元素,这个数组不就是有序的吗??按照这个思想我们可以使用递归将数组按照边界每一次分割,然后到最后每个数组都是有序的,我们可以先写出一次的排序,我相信大家已经写过类似与,两个有序数组合并的oj题了,
if (end == begin)
return;
int mid = (begin + end) / 2;
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;//这里一定要想清楚,不要用 - 否则会导致数组边界不对
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (_data[begin1] < _data[begin2])
{
temp[i++] = _data[begin1++];
}
else
{
temp[i++] = _data[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = _data[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = _data[begin2++];
}
在前面加上递归,跟快速排序不一样的是,快速排序是先排序,找到分割点,而而归并是先分割,找到有序的数组,再排序
void Sort::_merge_sort(int* temp, int begin, int end)
{
if (end == begin)
return;
int mid = (begin + end) / 2;
_merge_sort(temp, begin, mid);
_merge_sort(temp, mid + 1, end); //先分割,等在这个层面上有序后,在进行排序
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;//这里一定要想清楚,不要用 - 否则会导致数组边界不对
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (_data[begin1] < _data[begin2])
{
temp[i++] = _data[begin1++];
}
else
{
temp[i++] = _data[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = _data[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = _data[begin2++];
}
memcpy(_data + begin, temp + begin, sizeof(int) * (end - begin + 1));//赋值到原数组
}
注: 完整代码我之前不小心rm了,我之后所有文章的代码可以在这里找到
gif来源:sortAlgorithmGif/计数排序.gif at master · RongStudy/sortAlgorithmGif (github.com)