學習日記——排序算法C++實現

須知

算法使用模板進行編寫,有1個錯誤的排查並沒有完成,我會標註在代碼上的

遞歸——順序查找算法

因爲代碼十分簡短,故沒有對代碼邏輯進行描述,遞歸就行了

template<class T>
int sequential_search(T a[], int n,const T&x)
{
	if (n < 1)return -1;
	if (a[n - 1] == x)return n - 1;
	else return sequential_search(a, n - 1, x);
}

等級排序

這個算法的思想就是a[]爲輸入的亂序數組,n爲元素數量,r[]用來裝載數組a中每個元素在數組中的大小順序,但沒有對a進行排序移位,下邊還有一段被註銷的代碼,那個算法的效率是比上邊算法效率低的,然後下邊的rear_range函數就是用來對數組a依據數組r進行重排序的,n依然爲數組大小

//template<class T>
//a[]原本是T類的聲明
void rank_(int a[], int n, int r[])
{
	for (int i = 0; i < n; i++)
	{
		r[i] = 0;
	}//初始化
	for (int i = 1; i < n; i++)
	{
		for (int j = 0; j < i; j++)
		{
		if (a[j] <= a[i])
			r[i]++;
		else 
			r[j]++;
		}
	}
	//j從0開始,爲內循環,當j=i時停止內循環
	//i從1開始,每一個a[i]都和它前邊的i個數比較,大的順序自增,如a中第二個三,達成一次自增,則順序爲1
	//如果出現後邊還有小的數,如4,3,9,3,7,2
	//第一個3和前邊的4對比,4的序號加一,輪到2和4,3,9,3,7對比時,它們全增一
	//其實這就是一個排序
	//不同於書上的排序
	/*
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (a[j] >= a[i])
				r[j]++;
			else
				r[i]++;
		}
	}
	//此處j也進行n次循環,則r中最小的數會爲2,而且是兩個2
	//每一個a[i]都和所有的a對比,會出現和自己對比
	//自增一次,和另一個相同的數對比,自增一次,結果即2
	//故當數組內沒有相同的數時最小的排序值爲1
	//出現兩個相同的數時則爲2,以後同理,用j<i作爲條件可以避免
	//每一個a[i]只和它前邊的對比,不會和自身對比故從零自增
	//後邊相同的數和自己對比出現if中的條件
	//然後增的是後來的,不是內循環中的,故還能排序先出和後出現
	*/
template<class T>
void rear_range(T a[], int n, int r[])
{
	T*u[] = new T[n];
	for (int i = 0; i < n; i++)
		u[r[i]] = a[i];
		//把a中的數按照r得到的名次進行排序
		//即4去3號位,3去1號位,9去5號位,3去2號位,7去4號位
	for (int i = 0; i < n; i++)
		a[i] = u[i];
	delete[]u;
}
}

最大值函數和交換函數

實際上進行排序的時候,加入一些簡單的工具性函數更高效(寫代碼的時候)

int index_of_max(T a[], int n)
{//找最大值函數,n爲數組前n個數
	if (n <= 0)
	{
		cout << "error!" << endl;
		return - 1;
	}
	//從前n-1個數中找到最大的數,進行n-1次比較
	int indexofmax = 0;
	for (int i = 1; i < n; i++)
		if (a[indexofmax] < a[i])
			indexofmax = i;
	return indexofmax;
}
void swap(int&a,int&b)
{
	int	temp = a;
	a = b;
	b = temp;
}

選擇排序算法1

這個算法比較簡單暴力,很容易懂,找到最大的,放到最後,然後不管了,開始循環這樣的操作

template<class T>
void selection_sort(T a[], int n)
{//選擇排序
	for (int i = n - 1; i > 0; i--)
	{
		int j = index_of_max(a, i);
		//得到前i個數中最大的值的位置,下一次i自減
		swap(a[i], a[j]);
		//交換第i+1和最大值數的位置
		//此選擇排序執行次數每次都不會變,即總爲n-1次
	}
}

選擇排序算法2

這個算法設置了flag,可以用來減少排序次數,並且每次排序的時候會檢查整個數組是否完成排序,可以算是優化了吧

template<class T>
void selectionSort(T a[], int n)
{
	bool sorted = false;
	for (int size = n; !sorted && (size > 0); size--)
	{//從後往前放,從大放到小
		int index_of_max = 0;
		//不管最大的一開始是哪個,先從第一個開始
		sorted = true;
		//如果沒有出現逆序,就停止最外層循環
		for (int i = 0; i < size; i++)
		{//不管已經排序的部分,減少排序次數
			if (a[index_of_max] < a[i])index_of_max = i;
			//若出現當前index錯誤則糾正
			else sorted = false;
			//若內循環的全部次數裏出現了一次index錯誤即表示沒有排序完,需要再進行排序
		}
		swap(a[index_of_max], a[size - 1]);
		//對當前未排序的部分的最後一個數進行排序
	}
}

冒泡算法

相鄰比較,兩個相鄰的數大的向右移動,小的向左移動,就算冒個泡,冒泡次數很明顯是不會變化的

//template<class T>
void bubble(int	a[], int n)
{//把a[0:n-1]中最大元素移到最右邊
	for (int i = 0; i < n - 1; i++)
		if (a[i] > a[i + 1])
			swap(a[i], a[i + 1]);
}
//template<class T>
void bubble_sort(int a[], int n)
{
	for (int i = n; i > 1; i--)
		bubble(a, i);
}

及時終止的冒泡算法

變化在哪?冒泡算法用了bool型,設立了flag,如果一次冒泡中後邊的數存在已完成排序的情況,由flag表示冒泡完成,減少的排序次數應該是在冒泡排序算法上,如果某一次冒泡時已經是順序了,就不用冒泡

template<class T>
bool bubble(T a[], int n)
{
//及時終止的冒泡
	bool swapped = false;//先設置一個flag爲false
	for (int i = 0; i <= n - 1; i++)
	{
	//循環n次
		if (a[i] > a[i + 1])
		{
			swap(a[i], a[i + 1]);//傳統冒泡行爲
			swapped = true;//如果存在冒泡行爲即表示沒有完成排序
		}
	}
	return swapped;
}
template<class T>
void bubble_sort(T a[], int n)
{
	for (int i = n - 1; i > 0 && bubble(a, i); i--);
	//最多進行n次循環,如果存在沒有冒泡行爲就停止
	//即當bubble爲false,即完成排序,即可省略之後的循環次數
}

插入排序1

插入排序總共有三個算法,這是第一個

template<class T>
void insert0(T a[], int& n, const T&x)
{
//插入排序即在有序數組中插入一個元素
//其中n爲數組中元素個數,並不是數組最大容量
//數組最後一個數爲a[n-1]
	int i;//i需申明在循環以外,否則會報錯的
	for (i = n - 1; i >= 0 && x < a[i]; i--)
		a[i + 1] = a[i];//比x大的數往後移
	a[i + 1] = x;
	//跳出循環即x>=a[i],則有x應當處於a[i]後邊一位
	n++;//總數加一
}

插入排序2

插入排序總共有三個算法,這是第二個

template<class T>
void insert(T a[], int& n, const T&x)
{//插入排序即在有序數組中插入一個元素
	//其中n作爲最後一個數的標誌,即把x插入有序數組a[0:n-1]中
	//下邊用循環中變化的i表示有序數組的大小
	//其中n爲數組中元素個數,並不是數組最大容量
	//數組最後一個數爲a[n-1]
	int i;//i需申明在循環以外
	for (i = n - 1; i >= 0 && x < a[i]; i--)
		a[i + 1] = a[i];//比x大的數往後移
	a[i + 1] = x;
	//跳出循環即x>=a[i]
	//則有x應當處於a[i]後邊一位
}
template<class T>
void insert_sort(T a[], int n)
{
	for (int i = 1; i < n; i++)
	{
		T t = a[i];
		//獲得當前最後一個未排序的數值
		insert(a, i, t);
		//就像放麻將一樣,拿起最後未排序的數放入應該放的位置
		//所有比它大的數提前後移,這是插入與放麻將的一點區別
	}
}

插入排序3

插入排序總共有三個算法,這是第三個

template<class T>
void insertionsort(T a[],int n)
{
	for (int i = 1; i < n; i++)
	//內循環決定從後向前進行對比,則外循環需從零開始增加
	{
		T t = a[i];//當前未排序的數的值
		int j;
		for (j = i-1; j >=0 && (t < a[j]);j--)
		//①前面一個數跟後面一個數對比,從後向前比
		//只有當前未排序的數小於以此遇到的數才能繼續循環
		a[j+1]=a[j];
		//②因爲已經用t寄存當前插入數值,故可視後邊一個位置爲空
		//各個比當前數值大的數都後移,以留出當前數值的位置
		a[j + 1] = t;
		//③此時j記錄的是比當前數值小的數的位置
		//因爲當前數值要放在該位置之後一位,故用j+1
	}
}

歸併排序(正確)

這是網上借鑑來的一個算法,我用三目運算符簡化了一下代碼

//對已排好的兩個數組進行合併
void merge(int *arr, int low, int mid, int high)
{//從數組arr[low:high]
	int i, j, count = 0;
	int *data = new int[high - low + 1];
	for (i = low, j = mid + 1; i <= mid && j <= high; count++)data[count] = (arr[i] < arr[j]) ? arr[i++] : arr[j++];
	//進行循環直到for循環裏的條件被破壞,則一定會在下列兩種情況中選出一種
	for (; i <= mid;count++,i++)data[count] = arr[i];
	for (; j <= high;count++,j++)data[count] = arr[j];
	i = low;
	for (int k = 0; k < count&&i <= high; k++, i++)arr[i] = data[k];
	delete[] data;
}
//分而治之,將長數組一直進行二等分,當分到只剩一個時返回
void Msort(int* arr, int low, int high)
{
	if (low >= high){return;}
	int mid = (low + high) / 2;
	Msort(arr, low, mid);
	Msort(arr, mid + 1, high);
	merge(arr, low, mid, high);
}

歸併排序(故障)

這個算法來自《無處不在的算法》機械工業出版社,原本好像是java寫的,我用c++改了一下,但改來改去都不能出正確結果,有大佬會改的話求指教!我是真的盡力從理論上在修改過了一個晚上,但是奈何…其實放在這裏既算是一個記錄,也是一個求助

template<class T>
void merge(T a[], int al, int ar, T b[], int bl, int br, T c[])
{
//用三目運算符在ar和br之前把小的數放入c中,遇到超出ar或br就放沒超出的那個,執行次數爲兩個數組之和
//該函數用於把兩個有序的數組合併到一起
	int i = al, j = bl;
	for (int k = 0; k < ar - al + br - bl + 1; k++)
	{
		if (i < ar&&j < br)c[k] = (a[i] < b[j]) ? a[i++] : b[j++];
		if (i > ar)c[k] = b[j++];
		if (j > br)c[k] = a[i++];
	}
}
template<class T>
void merge_sort(T a[], int al, int ar)
{
	if (ar > al)
	{
		int m = (ar + al) / 2;//找到中間點
		merge_sort(a, al, m);
		//用遞推法把a在m的左右逐步分爲最小個數,然後執行下方排序
		merge_sort(a, m + 1, ar);
		T*b = new T[ar - al + 1];
		//聲明一個新數組臨時存儲有序的a
		merge(a, al, m, a, m + 1, ar, b);
		//把有序的數逐步合起來
		for (int i = 0; i < ar - al + 1; i++)
			a[al + i] = b[i];
	}
}

快速排序

代碼裏含有用於觀察數字如何變化的cout代碼,都已經註釋掉了,可以直接用噠

void quickSort(int *A, int al, int ar)
{
	if (al < ar)
	{
		int pivot = A[al];
		int i = al, j = ar + 1;
		while (true)
		{
			while (A[++i] > pivot&&i < ar) 
			{ 
				//cout << "達成 i 的移動" << endl; 
			};
			while (A[--j] < pivot&&j > al) 
			{ 
				//cout << "達成 j 的移動" << endl; 
			};
			if (i < j)
			{
				//cout << "交換" << A[i] << " 和 " << A[j] << endl;
				swap(A[i], A[j]);
			}
			else
				break;
		}
		//cout << endl << "還未移動pivot,此時已完成除pivot以外的所有數的移動,當前數組順序如下:" << endl;
		//for (int k = 0; k < ar - al + 1; k++)
			//cout << A[k] << " ";
		//cout << endl;
		//cout << " j= " << j <<" i= "<<i<< endl;
		//上方步驟用來把pivot兩邊的數值堆起來,兩邊的裏層順序並沒有排序
		swap(A[al], A[j]);
		//此步驟用於把pivot放到它應該在的位置
		//之後就是把pivot的左右兩邊用遞歸來排序的,也是再找各自的左邊第一個數爲pivot,然後把pivot再放到正確位置,再對這個pivot的左右進行排序,然後迴歸到最外層
		quickSort(A, al, j - 1);
		quickSort(A, j + 1, ar);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章