,基本排序

前言:

该文章用虽然用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了,我之后所有文章的代码可以在这里找到

zjsnh (github.com)

gif来源:sortAlgorithmGif/计数排序.gif at master · RongStudy/sortAlgorithmGif (github.com)

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值