1. 程式人生 > >【初探】“ 選擇排序 ” ——C++程式碼實現

【初探】“ 選擇排序 ” ——C++程式碼實現

 

選擇排序(Selection sort)是一種簡單直觀的排序演算法。無論什麼資料進去都是 O(n²) 的時間複雜度。所以用到它的時候,資料規模越小越好。唯一的好處可能就是不佔用額外的記憶體空間了吧。

 

簡單排序處理流程

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。

重複第二步,直到所有元素均排序完畢。

這裡寫圖片描述


演算法演示


 

如圖所示,每趟排序中,將當前第 i 小的元素放在位置 i 上。

選擇排序的主要優點與資料移動有關。

如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,因此對n個元素的表進行排序總共進行至多n-1次交換。在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。

 

下面看我的程式碼示例: 該程式碼中添加了一個標誌flag, 這樣當 資料是有序的時候,一次迴圈比較就可以了, 否則的話, 迴圈比較 N-1次, 大家可以我的程式碼。複製到編譯器上學, 效率更好。

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

class SqList
{
public:
	SqList(size_t sizeElem);
	~SqList();
	void printElem();
	void swapElem(int &a, int &b);
	void selectSort();
	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::selectSort()
{
	int count = 0;
	bool flag = true;
	for (int i = 0; i < m_length - 1 && flag; ++i)   //m_length - 1是因為陣列的下標是從0開始的
	{
		flag = false;
		int min = i;   //每次將min設定成無序陣列起始的位置元素的下標   
		for (int j = i + 1; j < m_length; ++j) /*遍歷無序陣列,找到最小元素。
											  一開始j從下標1開始,就是第二個數
											   j一開始迴圈的時候,就是無須陣列的第二個位置,i是無須陣列的第一個位置
											   j會隨著迴圈不斷的跟下標為i的資料進行比較
											   ,如果j位置的資料小,就跟i位置的資料交換*/
		{
			if (m_base[min] > m_base[j])
			{
				min = j; //如果發現比當前最小元素還小的元素,則更新記錄最小元素的下標,min始終記錄當前遵循中最小的元素下標
			}
		}
		if (min != i)  /*如果最小元素不是無序陣列起始位置元素,則與起始元素交換位置   ,
							   交換後當前最小的元素是到無序陣列的第一個數字,
							   那麼原來無序陣列的第一個數字則被交換到以前當前最小元素的位置*/
		{
			swap(m_base[min], m_base[i]);
			flag = true;
		}
		++count;
	}
	cout << " 選擇排序花了" << count << "趟完成了排序!" << endl;
}

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.selectSort();
				cout << "再輸出排序後的元素:";
				mySqList.printElem();
				break;
			}
			case 2:
				system("cls");
				cout << "程式已清屏!可以重新輸入!" << endl;
				break;
			default:
				cout << "輸入的序號不正確,請重新輸入!" << endl;
			}
		}
	}
	system("pause");
	return 0;
}

該陣列序列,花了5次迴圈比較,資料就正序了, 如果沒有flag, 要花8次。

 

不過下面的複雜度分析,寫的是簡單的選擇排序的複雜度, 上面的程式碼靈感,是來自標誌氣泡排序, 所以我就在選擇排序上,也加了個flag。 大家湊合著看吧。
 


複雜度分析


選擇排序的交換操作介於和 這裡寫圖片描述次之間。選擇排序的比較操作為 這裡寫圖片描述次之間。選擇排序的賦值操作介於和這裡寫圖片描述次之間。

比較次數O(n^2),比較次數與關鍵字的初始狀態無關,總的比較次數N=(n-1)+(n-2)+…+1=n*(n-1)/2。 交換次數O(n),最好情況是,已經有序,交換0次;最壞情況是,逆序,交換n-1次。 交換次數比氣泡排序少多了,由於交換所需CPU時間比比較所需的CPU時間多,n值較小時,選擇排序比氣泡排序快。

平均時間複雜度:O(n2), 這就意味值在n比較小的情況下,演算法可以保證一定的速度,當n足夠大時,演算法的效率會降低。並且隨著n的增大,演算法的時間增長很快。因此使用時需要特別注意。

穩定性:不穩定 (比如序列【5, 5, 3】第一趟就將第一個[5]與[3]交換,導致第一個5挪動到第二個5後面)
 

這裡寫圖片描述

 


時間複雜度


簡單選擇排序的比較次數與序列的初始排序無關。 假設待排序的序列有 N 個元素,則比較次數總是N (N - 1) / 2。

而移動次數與序列的初始排序有關。當序列正序時,移動次數最少,為 0.

當序列反序時,移動次數最多,為3N (N - 1) / 2。

所以,綜合以上,簡單排序的時間複雜度為 O(N2)。

空間複雜度

簡單選擇排序需要佔用 1 個臨時空間,在交換數值時使用,O(1) (用於交換和記錄索引)