[Sword Finger Offer]-スタックとキューに関連する演習

まず、2つのスタックを使用してキューを実装します

(1)トピックの要件
キューを実装するには、2つのスタックを使用します。2つの関数appendTailとdeleteHeadを実装して、キューの末尾にノードを挿入し、キューの先頭にあるノードを削除する機能を完了してください。

template<typename T>
class CQueue
{
public:
	CQueue(void);
	~CQueue(void);

	void appendTail(const T& node);
	T deleteHead();

private:
	stack<T> stack1;
	stack<T> stack2;
};

(2)問題分析
スタック構造の特性は「先入れ先出し」であり、キュー構造の特性は「先入れ先出し」であることがわかっているので、この問題を注意深く分析してください。先入れ先出し構造。

以下では、特定の例を通じて、要素をキューに挿入および削除するプロセス全体を分析します。まず、要素aをstack1に挿入します。現時点では、stack2の要素は空ではありません。次に、要素b、cをstack1に挿入し続けます。このときの状況を図1に示します。
この時点では、キューの削除操作には先入れ先出しが必要なので、キュー内の要素を削除する操作を完了しようとします。この機能によれば、最初に要素を削除する必要がありますが、要素aはスタック1の一番下にあります。したがって、操作を完了するには、stack2を借りる必要があります。最初に、stack1のすべての要素をスタックからプッシュしてから、stack2にプッシュします。この時点で、stack2の一番上の要素はaであり、その後、スタックから再び抜け出すことができます。このときの状況を次の図2に示します。
ここに画像の説明を挿入
この時点で、デキュー操作の原理を要約できます。stack2が空でない場合、スタックの一番上の要素がデキュー要素としてポップアップします。stack2が空の場合、要素を最初にstack1にプッシュする必要があります。 stack1の要素が1つずつstack2にポップされます。

上記の操作に基づいて、次の図3に示すように、別の要素bをデキューします。結論が正しいことを確認するために、ケース3で要素dをエンキューしてからデキューします。状況は図4のように変化します。
ここに画像の説明を挿入
上記の説明により、次のようにコードを記述できます。

template<typename T>
void CQueue<T>::appendTail(const T& node)
{
	stack1.push(node);
}

template<typename T>
T CQueue<T>::deleteHead()
{
	if (stack2.size() <= 0)
	{
		while (stack1.size() > 0)
		{
			T& data = stack1.top();
			stack1.pop();
			stack2.push(data);
		}
	}
	if (stack2.size() == 0)
	{
		throw new exception("queue is empty");
	}
	T head = stack2.top();
	stack2.pop();
	return head;
}

次に、2つのキューを使用してスタックを実装します

前の質問の分析と同様に、この問題を解決するための核心は、「先入れ先出し」構造を使用して「先入れ先出し」プロセスを実現することです。実際、本質は2つのキュー間の前後のエンキューおよびデキュー操作です。特定のプロセスを次の図に示します。
ここに画像の説明を挿入
スタッキングプロセスはエンキューのプロセスですが、デキュー時にデキューされる最初の要素はスタックの最後の要素ですdなので、dの前に3つの要素a、b、cを保存するには別のキューが必要です。要素cを残すプロセスは同じです。このとき、別の要素eがスタックにプッシュされてから、スタックからプッシュされます。次に、キュー内の要素eの前の要素をキュー2にデキューし、要素eを再度デキューする必要があります。
上記のプロセスの説明により、次のようにコードを描画できます。

template<typename T> class CStack
{
public:
	CStack(void);
	~CStack(void);

	void appendTail(const T& node);
	T deleteHead();

private:
	queue<T> q1;
	queue<T> q2;
};

template<typename T> 
void CStack<T>::appendTail(const T& node)
{
	if (!q1.empty())
		q1.push(node);
	else
		q2.push(node);
}

template<typename T>
T CStack<T>::deleteHead()
{
	int ret = 0;
	if (!q1.empty())
	{
		int num = q1.size();
		while (num > 1)
		{
			q2.push(q1.front());
			q1.pop();
			num--;
		}
		ret = q1.front;
		q1.pop();
	}
	else
	{
		int num = q2.size();
		while (num > 1)
		{
			q1.push(q2.front());
			q2.pop();
			num--;
		}
		ret = q2.front;
		q2.pop();
	}
	return ret;
}

さらに、キューを使用してスタックを実装し、スタックを使用してキューを実装するソリューションがあります。詳細については、スタックとキューの相互実装について書いた別のブログ投稿を参照してください

3番目に、min関数を含むスタック

(1)タイトル要件
スタックのデータ構造定義するには、スタックに含まれる最小の要素を取得できるこのタイプのmin関数を実装してください(時間の複雑さはO(1)でなければなりません)。
注:スタックが空の場合、テストでスタックのpop()またはmin()またはtop()が呼び出されないことを確認してください。
(2)問題分析
方法1:
まず、この質問が出たら、新しい要素をスタックにプッシュするたびにスタック内のすべての要素をソートし、最小値をスタックに返すことができます。時間の複雑さがO(1)であることを保証する最上位の要素の値ですが、この考え方を慎重に検討しても、スタックにプッシュされた最後の要素が最初にスタックからプッシュされることは保証されません。これは、スタックデータ構造
メソッド2に準拠していません。
最小の要素を格納するメンバー変数を追加します。スタック上の要素がスタックにプッシュされるたびにメンバー変数の値よりも小さい場合、メンバー変数の値が更新されますが、スタックから最小値をプッシュする方法を考えましたその後、残りのスタックの最小値を決定する
方法方法3:
2番目の方法の考え方に基づいて、メンバー変数を補助スタックに変更して、スタックに最小値を格納できます。特定の操作について、正確な例を使用して説明します。次の表は、3、4、2、1がスタックにポップされ、スタックの最上位が2回ポップされ、次に0が押された場合のデータスタック補助スタックの関連状況と最小値を示しています。
ここに画像の説明を挿入
上記の表からわかるように、最小要素が補助スタックに毎回プッシュされる場合、補助スタックの最上部が常に最小要素であることが保証されます。
上記のアイデアに基づいて、pop、push、minのコアコードは次のとおりです。

template <typename T>
void StackWithMin<T>::push(const T& value)
{
	m_data.push(value);

	if (m_min.size() == 0 || value < m_min.top())
		m_min.push(value);
	else
		m_min.push(m_min.top());
}

template <typename T>
void StackWithMin<T>::pop()
{
	assert(m_data.size() > 0 && m_min.size() > 0);

	m_data.pop();
	m_min.pop();
}

template <typename T>
const T& StackWithMin<T>::min() const
{
	assert(m_data.size() > 0 && m_min.size() > 0);
	return m_min.top();
}

第4に、スタックのプッシュとポップのシーケンス

(1)質問の要件
2つの整数シーケンスを入力します。最初のシーケンスはスタックシーケンスを示します。2番目のシーケンスがスタックのポップシーケンスであるかどうかを確認してください。スタックにプッシュされたすべての数値が等しくないと仮定します。たとえば、シーケンス1,2,3,4,5はスタックのプッシュシーケンスであり、シーケンス4,5,3,2,1はスタックシーケンスに対応するポップアップシーケンスですが、4,3,5,1,2です。プッシュシーケンスのポップシーケンスにすることはできません。(注:2つのシーケンスの長さは同じです)
(2)
問題分析分析プロセス全体は、分析用のポップアップシーケンスとして特定のシーケンス4,5,3,2,1に基づいています。まず、ポップする数は4であるため、4を補助スタックにプッシュする必要があり、プッシュシーケンスはスタックシーケンスによって決定されるため、最初に1、2、3を補助スタックにプッシュする必要があります。このとき、補助スタック要素は1、2、3、4です。スタックの一番上にある要素は、ポップする数値4で、補助スタックで4をポップします。次に、ポップする数は5です。このとき、彼は補助スタックの一番上の要素ではないので、スタックシーケンスの5を補助スタックにプッシュする必要があります。このとき、5はスタックの一番上にあり、ポップしてから続行できます。上記と同じ動作です。図は次のとおりです。
ここに画像の説明を挿入
一方、分析するポップアップシーケンスとして、特定のシーケンス4,3,5,1,2を使用します。マッチング処理の前に、上記と同じであるが、数は配列がポップアップ配列することができないように、エジェクタ1は、スタックのトップが一致せず、プッシュ配列要素が存在しない最後の時間である
ここに画像の説明を挿入
上記2つと特定の分析については、次のようにコードを記述できます。

bool IsPopOrder(const int* Ppush, const int* Ppop, int nlength)
{
	bool bpossible = false;
	if (Ppush != nullptr && Ppop != nullptr && nlength > 0)
	{
		const int* pNextPush = Ppush;
		const int* pNextPop = Ppop;

		stack<int> stackData;//定义一个辅助栈

		while (pNextPop - Ppop < nlength)
		{
			while (stackData.empty() || stackData.top() != *pNextPop)
			{
				if (pNextPush - Ppush == nlength)//如果已经把压栈元素全部压入
					break;

				stackData.push(*pNextPush);//如果辅助栈中的栈顶元素不等于弹出序列Ppop的栈顶元素
				pNextPush++;
			}
			if (stackData.top() != *pNextPop)//如果所有数字都压入过后,仍然没有找到下一个弹出的数字
				break;

			stackData.pop();//匹配成功,辅助栈弹出寻找下一个弹出的数字
			pNextPop++;
		}

		if (stackData.empty() && pNextPop - Ppop == nlength)
			bpossible = true;//辅助栈为空并且弹出序列遍历完毕
	}
	return bpossible;
}
公開された98元の記事 ウォンの賞賛9 ビュー3642

おすすめ

転載: blog.csdn.net/qq_43412060/article/details/105486224