【初探】“直接插入排序”—— 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)的額外記憶體空間。
● 它是線上排序,可以邊接收資料邊排序。
● 它跟我們牌撲克牌的方式相似。
● 對小資料集是有效的。