ソート(1)交換ソートc / c ++およびpython実装

為替注文

交換とは、シーケンス内の2つのキーワードの比較結果に従って、シーケンス内のこれら2つのレコードの位置を交換することで、主にバブルソートとクイックソートがあります。

バブルソート(Bubble Sort)

バブルソートの基本的な考え方:前面から背面へ、または背面から前面へ、2つの隣接する要素を比較し、順序が逆の場合は交換します。各バブルソートは、少なくとも1つの要素を本来あるべき場所に移動し、n-1を繰り返し、n個のデータのソートが完了します。

データのグループ7、8、9、6、5、4を小さいものから大きいものに並べ替える場合、最初のバブルソートの詳細なプロセスは次のとおりです。
ここに画像の説明を挿入バブル操作の後、要素がn-1個のバブリング操作の後、n-1個の要素が本来あるべき位置に移動し、残りの要素は当然その位置にあります。

実際、バブリングプロセスは今すぐ最適化できます。バブリング操作のためのデータ交換がない場合、それは完全な順序に達したことを意味し、後続のバブリング操作を実行し続ける必要はありません。

// 冒泡排序c实现,a表示数组,n表示数组大小
/**
 * Author: gamilian
*/
void bubble_sort(int a[], int n) {
	if (n <= 1) 
		return;
 	for (int i = 0; i < n; ++i) {
    	boolean flag = false;	// 提前退出冒泡循环的标志位
    	for (int j = 0; j < n - i - 1; ++j) {
      		if (a[j] > a[j+1]) { // 交换
        		int tmp = a[j];
       			a[j] = a[j+1];
        		a[j+1] = tmp;
        		flag = true;  // 表示有数据交换      
      		}
   	 	}
    	if (!flag) 
    		break;  // 没有数据交换,提前退出
  	}
}
#	冒泡排序python实现
"""
    Author: gamilian
"""
def bubble_sort(a):
	""" 冒泡排序 
		args:
			a: List[int]
	"""
    length = len(a)
    if length <= 1:
        return

    for i in range(length):
        made_swap = False
        for j in range(length - i - 1):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                made_swap = True
        if not made_swap:
            break

アルゴリズムの安定性:バブルソートでは、交換のみが2つの要素の順序を変更できます。バブル並べ替えアルゴリズムの安定性を確保するために、同じサイズの2つの隣接する要素がある場合、交換は行われません。同じサイズのデータ​​は並べ替えの前後で順序を変更しないため、バブル並べ替えは安定した並べ替えアルゴリズムです。

スペースの複雑さ:バブリングプロセスは、隣接するデータの交換のみを含み、一定レベルの一時スペースのみを必要とするため、そのスペースの複雑度はO(1)であり、インプレースソートアルゴリズムです。

時間の複雑さ:最良の場合、並べ替えられるデータは既に順序付けられています。バブリング操作を実行するだけでデータを終了できるため、時間の複雑さはO(n)です。最悪の場合、並べ替えられるデータが逆の順序になることがあります。n-1個のバブリング操作を実行する必要があるため、最悪の場合の時間の複雑さはO(n ^ 2)です。平均時間の複雑さはより複雑であり、逆の次数で計算できます。
逆順の度合いは、順序付けられていない関係を持つ配列内の要素のペアの数です。

逆の順序の要素のペア:i <jの場合、a [i]> a [j]。

順序の次数は、順序付けられた関係を持つ配列内の要素のペアの数です。

順序付けられた要素のペア:a [i] <= a [j](i <jの場合)。

完全に配列された配列の場合、配列の次数はn *(n-1)/ 2です。このような完全に配列された配列の次数を完全な次数と呼びます
同時に、逆次数=完全次数-次数

バブルソートには、比較と交換という2つの操作アトムが含まれます。交換が発生するたびに、次数は1つずつ増加します。アルゴリズムがどのように改善されても、交換の数は常に決定されます。これは逆順次数、つまりn *(n-1)/ 2-初期次数です。

n個のデータを含む配列に対してバブルソートを実行しますが、最悪の場合、初期状態のn個のデータの順序が逆で、順序の次数が0であるため、n *(n-1)/ 2回の交換が行われます。最良のケースでは、初期状態データが順序付けされ、順序の次数はn *(n-1)/ 2なので、交換する必要はありません。平均すると、n *(n-1)/ 4のスワップ操作が必要であり、スワップ操作よりも比較操作の方が明らかに多く、複雑度の上限はO(n ^ 2)なので、平均時間の複雑度はO(n ^ 2)。

クイックソート

クイックソートの考え方は、分割統治の考え方に基づいています。配列の添え字を使用して、pからrまでのデータのセットをソートする場合、ピボット(パーティションポイント)としてpからrまでのデータを選択します。

pとrの間でデータをトラバースし、ピボットより小さい値を左に、ピボットより大きい値を右に、ピボットを中央に配置します。このステップの後、配列pからrのデータは3つの部分に分割され、前のpからq-1はピボットより小さく、中央はピボットであり、次のq + 1からrはピボットより大きい。
ここに画像の説明を挿入

分割統治と再帰処理の考え方によれば、pからq-1までの添え字を持つデータとq + 1からrまでの添え字を持つデータの再帰的ソートを、間隔が1に減少するまで使用できます。データは整然としている。

再帰式:quick_sort(左...右)= quick_sort(左...ピボット-1)+ quick_sort(ピボット+1 ...右)
終了条件:左> =右

//	快排c实现
/**
 * Author: gamilian
*/
//	对区间[left,right]划分
int Partition(int A[], int left, int right){
	int pivot = A[left];	//将第一个元素设为pivot
	while(left < right){	//只要left与right不相遇
		while(left < right && right > pivot) right--;	
		//只要right比pivot大,就一直左移
		A[left] = A[right];	//将比pivot小的元素移到左边
		while(left < right && left <= pivot) left++;
		//只要left比pivot小,就一直右移
		A[right] = A[left];	//将比pivot大的元素移到右边
	}
	A[left] = pivot;		//pivot放在最终left与right相遇的位置
	return left;			//返回存放pivot的下标
}
// A是数组,left与right初值为序列首尾下标
void quick_sort(int A[], int left, int right){ 
	if(left < right){						//当前区间长度超过1
		int pivot = Partition(A, left, right);	//划分区间
		quick_sort(A, left, pivot - 1);			//对于左子区间快排
		quick_sort(A, pivot + 1, right);		//对右子区间快排
	}
}
# 快排python实现,划分时用swap
"""
    Author: gamilian
"""
import random
def quick_sort(A, left, high):
	""" 快速排序 
		args:
			A: List[int]
			left: int
			right: int
	"""
    if left < right:
        # get a random position as the pivot
        k = random.randint(left, high)
        a[left], a[k] = a[k], a[left]

        m = partition(a, left, high)  # a[m] is in final position
        quick_sort(a, left, m - 1)
        quick_sort(a, m + 1, high)


def partition(a, left, right):
	""" 划分区间 
		args:
			A: List[int]
			left: int
			right: int
	"""
    pivot, j = a[left], left
    for i in range(left + 1, right + 1):
        if a[i] <= pivot:
            j += 1
            a[j], a[i] = a[i], a[j]  # swap
    a[left], a[j] = a[j], a[left]
    return j

アルゴリズムの安定性:分割プロセスにはスワップ操作が含まれるため、配列に2つの同一の要素がある場合、最初の分割操作の後に、2つの同一の要素の相対的な順序が変わります。したがって、クイックソートは安定したソートアルゴリズムではありません

スペースの複雑さ:再帰的ワークスタックがカウントされる場合、高速キューは再帰的であるため、再帰的コールの各層の必要な情報を保存するために再帰的ワークスタックが必要であり、その容量は再帰的コールの最大深度と一致します。高速キューの最良の場合のスペースの複雑度はO(nlogn)であり、最悪の場合はn-1回の再帰呼び出しが必要です。つまり、高速キューの最悪の場合のスペースの複雑度はO(n)あり、高速キューの平均スペース複雑度はO(nlogn)
再帰的なワークスタックがカウントされない場合、高速ソートスペースの複雑度はO(1)です。これは、インプレースソートアルゴリズムです。

時間の複雑さ:各パーティション操作で配列をほぼ同じサイズの2つのセルに分割できる場合、時間の複雑さを再帰的に解決するための式はマージと同じです。したがって、高速ソートの最良のケースはO(nlogn)です。

T(1)= C; n = 1の場合、一定レベルの実行時間が必要なだけなので、Cとして表されます。T(n)= 2 * T(n / 2)+ n; n> 1

毎回最後の要素をピボットとして選択すると、各パーティションによって取得される2つの間隔は等しくありません。クイックキューのプロセス全体を完了するには、約n個のパーティション操作を実行する必要があります。パーティションごとに平均で約n / 2の要素をスキャンする必要があるため、高速ソートの最悪の場合の複雑さはO(n ^ 2)です。

各パーティション操作で、間隔をサイズが9:1の2つのセルに分割するとします。

T(1)= C; n = 1の場合、一定レベルの実行時間が必要なだけなので、Cとして表されます。
T(n)= T(n / 10)+ T(9 * n / 10)+ n; n> 1

したがって、高速キューの平均時間の複雑さはO(nlogn)です。

オリジナルの記事を10件公開 Likes2 訪問数496

おすすめ

転載: blog.csdn.net/qq_41167295/article/details/105112905
おすすめ