【初探】“直接插入排序”—— C++代码实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/84441224

目录

直接插入排序简介

算法步骤

算法演示

复杂度分析

稳定性

直接插入排序算法的特点



直接插入排序简介


● 插入排序是一种简单直观的排序算法,它也是基于比较的排序算法。它的工作原理是通过不断扩张有序序列的范围,对于未排序的数据,在已排序中从后向前扫描,找到相应的位置并插入。插入排序在实现上通常采用就地排序(不占用额外内存或占用常数的内存,当需要大量数据排序时,占用内存非常少。),因而空间复杂度为O(1)。在从后向前扫描的过程中,需要反复把已排序元素逐步向后移动,为新元素提供插入空间,因此插入排序的时间复杂度为O(n^2);

● 插入排序是基于比较的排序。所谓的基于比较,就是通过比较数组中的元素,看谁大谁小,根据结果来调整元素的位置。

因此,对于这类排序,就有两种基本的操作:①比较操作; ②交换操作

其中,对于交换操作,可以优化成移动操作,即不直接进行两个元素的交换,还是用一个枢轴元素(temp)将当前元素先保存起来,然后执行移动操作,待确定了最终位置后,再将当前元素放入合适的位置。(下面的插入排序就用到了这个技巧)–因为,交换操作需要三次赋值,而移动操作只需要一次赋值!

有些排序算法,比较次数比较多,而移动次数比较少,而有些则相反。比如,归并排序和快速排序,前者移动次数比较多,而后者比较次数比较多。

这里主要介绍插入排序


算法步骤


从第一个元素开始,该元素可以认为已经被排序

取出下一个元素,在已经排序的元素序列中从后向前扫描

如果该元素(已排序)大于新元素,将该元素移到下一位置

重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

将新元素插入到该位置后

重复步骤2~5

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。

最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。

最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数减去(n-1)次。平均来说插入排序算法复杂度为O(n^2)。


算法演示


假设我们要对数组{12,4,5,2,6,14}进行插入排序,排序过程为:

这里写图片描述

算法动态示意图: 

下面看代码示例:

#include<iostream>
#include<cassert>
using namespace std;

class SqList
{
public:
	SqList(size_t sizeElem);
	~SqList();
	void printElem();
	void swapElem(int &a, int &b);
	void insertSort();
	void create(const size_t length);
private:
	int *m_base; //指向数组
	int m_length;  //记录数组中的个数
};

SqList::SqList(size_t sizeElem)
{
	m_base = new int[sizeElem];
	assert(m_base != nullptr);
	m_length = 0;
}
SqList::~SqList()
{
	delete m_base;
	m_base = nullptr;
}

void SqList::create(const size_t length)
{
	m_length = length;
	cout << "请分别输入你想排序的这" << length << "个元素,中间以回车键隔开:\n";
	for (size_t i = 0; i != length; ++i)
	{
		cin >> m_base[i];
	}
	cout << endl;
}
void SqList::printElem()
{
	for (size_t i = 0; i != m_length; ++i)
	{
		cout << m_base[i] << " ";
	}
	cout << endl;
}
void SqList::swapElem(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
}
void SqList::insertSort()
{
	/* 这个代码是把要无须数组的第一个数据用temp保存起来, 
	然后用循环来移动数据,最后把该数据放到有序数组的相应位置*/
	int count = 0;
	
	for (int i = 1; i < m_length ; ++i)  //从第2个数据开始插入  
	{
		
		int j = i - 1; // j记录的是有序数组的最后一个位置,从后面往前面找
		int temp = m_base[i];  //记录要插入的数据 ,temp 每次循环记录无须数组的第一个数字
		while (0 <= j &&  temp < m_base[j] )  //从后向前,找到比其小的数的位置  
		{
			m_base[j + 1] = m_base[j];   //向后挪动  
			--j;
		}
		if (j != i - 1)  //存在比其小的数(其实这个if语句去掉,直接后面的语句也是可以的)  
		{                // 这里的j 就是表示要插入位置之前一个位置的下标
			m_base[j + 1] = temp;  //把要插入的数据插入合适的位置
		}
		++count;
	}
	cout << "直接插入排序花了" << count << "次完成了排序!" << endl;
	/* 这块代码用的是交换数据, 意思就是说 把无须数组的第一个元素直接交换到有序数组的相应位置,没有移动元素
	for (int i = 1; i < m_length; ++i)
	{
		for (int j = i; 0 < j; --j)
		{
			if (m_base[j] < m_base[j - 1])
			{
				swapElem(m_base[j], m_base[j - 1]);
			}
			else
				break;
		}
	}*/
}

int main()
{
	{
		int sizeCapacity(0);
		cout << "输入数组的最大容量:";
		cin >> sizeCapacity;
		SqList mySqList(sizeCapacity);

		while (true)
		{
			{
				cout << "\n************************ 欢迎来到来到直接插入排序的世界!**********************\n" << endl
					<< "输入0,退出程序!" << endl
					<< "输入1,进行直接插入排序!" << endl
					<< "输入2,清屏!" << endl;
			}

			cout << "************************* 请输入你想要使用的功能的序号 **********************" << endl;
			int select(0);
			cout << "请输入你的选择:";
			cin >> select;
			if (!select)
			{
				cout << "程序已退出,感谢你的使用!" << endl;
				break;
			}
			switch (select)
			{
			case 1:
			{
				cout << "请输入你想排序数组元素的个数:";
				int arraySize(0);
				cin >> arraySize;
				assert(arraySize != 0);

				mySqList.create(arraySize);
				cout << "先输出排序前的元素:";
				mySqList.printElem();

				mySqList.insertSort();
				cout << "再输出排序后的元素:";
				mySqList.printElem();
				break;
			}
			case 2:
				system("cls");
				cout << "程序已清屏!可以重新输入!" << endl;
				break;
			default:
				cout << "输入的序号不正确,请重新输入!" << endl;
			}
		}
	}
	system("pause");
	return 0;
}


复杂度分析


● 插入排序的时间复杂度 就是判断比较次数有多少,而比较次数与 待排数组的初始顺序有关。 最好情况下,排序前对象已经按照要求的有序。比较次数(n−1) ; 移动次数(0)次。则对应的时间复杂度为O(n)。

● 最坏情况是数组逆序排序,第 i 趟时第 i 个对象必须与前面 i 个对象都做排序码比较,并且每做1次比较就要做1次数据移动,此时需要进行 (n +2)*(n-1) / 2次比较;  而记录的移动次数也达到最大值 (n+4)*(n-1)/2 次。 则对应的时间复杂度为这里写图片描述
 

● 如果排序记录是随机的,那么根据概率相同的原则,在平均情况下的排序码比较次数和对象移动次数约为这里写图片描述,因此,直接插入排序的时间复杂度为这里写图片描述。  同样的 这里写图片描述  时间复杂度,直接插入排序比冒泡和简单选择排序的性能好一些。

插入排序不适合对大量数据进行排序应用,但排序数量级小于千时插入排序的效率还不错,可以考虑使用。

直接插入排序采用就地排序,空间复杂度为O(1).

其实,插入排序的比较次数与数组的逆序数相关,因为插入排序在将某个元素插入到合适位置时,其实就是消除这个元素的逆序数。
 


稳定性


直接插入排序是稳定的,不会改变相同元素的相对顺序。


直接插入排序算法的特点


● 它是稳定排序,不改变相同元素原来的顺序。

● 它是就地排序,只需要O(1)的额外内存空间。

● 它是在线排序,可以边接收数据边排序。

● 它跟我们牌扑克牌的方式相似。

● 对小数据集是有效的。

猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/84441224