1. 程式人生 > >【初探】“直接插入排序”—— C++程式碼實現

【初探】“直接插入排序”—— C++程式碼實現

目錄

直接插入排序簡介

演算法步驟

演算法演示

複雜度分析

穩定性

直接插入排序演算法的特點



直接插入排序簡介


● 插入排序是一種簡單直觀的排序演算法,它也是基於比較的排序演算法。它的工作原理是通過不斷擴張有序序列的範圍,對於未排序的資料,在已排序中從後向前掃描,找到相應的位置並插入。插入排序在實現上通常採用就地排序(不佔用額外記憶體或佔用常數的記憶體,當需要大量資料排序時,佔用記憶體非常少。),因而空間複雜度為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)的額外記憶體空間。

● 它是線上排序,可以邊接收資料邊排序。

● 它跟我們牌撲克牌的方式相似。

● 對小資料集是有效的。