[ねじとナットの問題] [アルゴリズムの分析と設計]直径の異なるn個のねじとn個の対応するナットがあるとします。

教科書オリジナルタイトル

直径の異なるネジがn個、対応するナットがn個あるとします。我々は唯一の一対比較することができ、ネジナットをねじよりも小さく、ナットがねじよりも大きいかどうかを判断する時に、または単にネジに適合する。ただし、2つのナットを比較することはできません。また、2つのネジを比較することできません

私たちの問題は、一致するネジとナットのすべてのペア見つけることです。この問題のアルゴリズムを設計するには、その平均効率がΘ(n log n)に属している必要があります。

質問分析

トピックを読んだ後、私はそれが私たちに何をしてほしいのかわからないかもしれません、そして入力または出力の要件はありません。これは、私たちが行うojの質問とは大きく異なります。ojの質問は、指定された時間とスペース内で正しい答えを出すだけで済みます。そして、この種のアルゴリズム分析の設計問題、入力、出力、アルゴリズム、およびコードはすべて私たちによって設計されています。

一部の生徒は、ネジとナットの2つの配列でクイックソートを直接実行し、結果を出力する場合があります。明らかに、高速ソートは個別に上記の質問の要件に違反します。

アルゴリズムを正式に設計してコードを記述する前に、入力と出力について考えるだけです。入力はナットとネジの2つの配列である必要があります。出力はより単純で、2つのソートされた配列を直接出力できますが、中央のアルゴリズムは質問の要件を満たしています。

まだ質問があります!分割するとき、要素の添え字は絶えず変化しており、最初はネジとナットに番号を付けていませんでした。このように入力と出力を行いますが、上の太字でマークされている「検索」をどのように反映しますか?出力は2つの順序付けられた配列であり、1対1の対応により、「検索」と「一致」を示すことができます。実際と同じように、番号を特定せずに手動で位置を移動することで、一致するネジとナットを直接組み合わせることができます。

なぜなら、実際にコードを書く前に、特定の論理分析が必要だからです。

アルゴリズムの分析

 最初にランダムにネジAを選択し、間隔の左端のネジAを選択するとします(最初の間隔は[0、n-1]です)。次に、ナットの対応する間隔([0、n-1])でねじAに対応するナットaを見つけます。次に、ナットaと間隔の左端のナットの位置を変更します。次に、ネジAに従って、間隔でナットaを分割します。これは、ナットaの位置の変更ではなく、ネジAに基づいていることに注意してください。そうでなければ、それは質問の意味に違反するでしょう。ナットを一度割った後。同様に、ネジはナットaで分割されます。この時点での間隔はまだ[0、n-1]であることに注意してください。

上記は除算アルゴリズムです。複数回分割する必要があり、分割するたびに間隔を狭める必要があります。詳細については、コードを参照してください。

高速キューのテンプレートコード

クイックソート用のテンプレートコードを最初に投稿します。アルゴリズムの考え方は同じですが、この実装は教科書よりも少し簡単です。

void quicksort(int x[],int left,int right)  //快速排序 
{
    if(left<right)
    {
        int i=left,j=right,key=x[left];
        while(i<j)
        {
            while(i<j&&x[j]>=key)
                j--;
            if(i<j)
                x[i++]=x[j];

            while(i<j&&x[i]<=key)
                i++;
            if(i<j)
                x[j--]=x[i];
        }
        x[i]=key;
        
        quicksort(x,left,i-1);
        quicksort(x,i+1,right);
    }
}

問題解決コード

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 9;

void print(int ld[], int lm[]) {
	printf("ld:");
	for (int i = 0; i < N; i++) {
		printf("%d ", ld[i]);
	}
	printf("\n");
	printf("lm:");
	for (int i = 0; i < N; i++) {
		printf("%d ", lm[i]);
	}
	printf("\n");
}

// 进行一次划分 
// [l, r] { 5, 2, 4, 3, 1 }
int partition(int x[], int l, int r, int key) {
	
	// 假设key是螺钉,则首先在螺母中找到与之匹配的螺母
	// 然后与最左侧的螺母交换,使之称为中轴,方便之后作partition 
	for(int i=l; i<=r; i++)
		if(x[i]==key)
		{
			swap(x[i], x[l]);
			break;
		}
	
	// 以key划分螺母 
	int i=l,j=r;
	int t=x[l]; // 暂存中轴 
    while(i<j)
    {
        while(i<j&&x[j]>=key) // 注意,这里要与key做比较,不要用t来比较 
            j--;
        if(i<j)
            x[i++]=x[j];

        while(i<j&&x[i]<=key)
            i++;
        if(i<j)
            x[j--]=x[i];
    }
    x[i]=t;
    
//    printf("i=%d\n", i);
//    for(int i = l; i <= r; i++ ) {
//    	printf("%d ", x[i]);
//	}
	return i;
} 

// [l, r]
void match(int ld[], int lm[], int l, int r) {
	
	if (l < r) {
		// 随便选一个 ld,此处以区间的最左侧(第1个)那个 ld 为例子 
		int ldKey = ld[l];
		// 返回划分之后 lm 的位置 
		// 此时与选的ld对应的lm,(划分之后)在下标为 pos 的位置
		int pos1 = partition(lm, l, r, ldKey);
		
		// 选这个 lm,来划分 ld 
		int lmKey = lm[pos1]; 
        int pos2 = partition(ld, l, r, lmKey); 
        
        // 在这里pos1与pos2相等的 
        print(ld, lm);
        printf("pos1=%d pos2=%d\n", pos1, pos2);
       
		match(ld, lm, l, pos1-1); 
		match(ld, lm, pos1+1, r); 
	}
}


int main() {
    // 为了方便模拟,螺钉和螺母都以数字1-9编号
    // 而且,约定编号越大,直径越大
	int ld[N] = { 5, 9, 3, 7, 1, 8, 2, 4, 6 };
	int lm[N] = { 7, 1, 4, 2, 5, 6, 9, 8, 3 };
		
	match(ld, lm, 0, N-1);
	
	return 0;
}

「注意、ここではキーと比較されます。比較するためにtを使用しないでください」

コメントがわからない人もいるかもしれません。ネジとナットは同じ番号で表されているため、違いが感じられない場合があります。

ねじを表すために小文字を使用し、ナットを表すために大文字を使用するとします。次に、上記のコメントと元の質問をよりよく理解できるように、上記のコードの比較を変更する必要があります。

おすすめ

転載: blog.csdn.net/qq_43290318/article/details/109141684