個々の公共の番号でこの記事の発信元:TechFlow、オリジナリティが信者を求めて、簡単ではありません
定義されたスタック
今日は、もともと迅速な選択アルゴリズムについて話をしたかったが、関連書かれたいくつかの連続したソートを見つけたので、一時的に、今日のポイントを話して、件名を変更するデータ構造、古典的な、シンプルなデータ構造を見てみましょう - スタック。
「スタック」と呼ばれる私たちは、すべて一緒に、特にヒープの多くの部分では、精通して結ばれるべきだと思うスタック構造は、さらに広く公表されています。ヒープとスタックの本質上の2つの異なるデータ構造です。しかし実際には、我々は単純に混同することはできません。のは、比較的単純なスタックの先頭から始めましょう。
スタックとキューは、実際には、アレイ(厳密に言えば、リニアテーブル)の本質です。データ構造の多くは、学生が少しリラックスすることができます臆病なので、それは、唯一何も特別なスタックのトリックを特定の条件を満たしているように、しかし、我々は、アレイ上のいくつかの制限を追加し、配列の特別な種類です。
線形及びアップ以外の一般化されたテーブルデータ構造、2つのだけのスタック特異性は、他のは、アレイの片側のみから読み出され、最後のアウトです。私たちは読み取り専用と片側からの書き込み要素、早く出て夜に、当然のように、最後のアウトすることができますので、しかし、これらの二つは、本質的に同じです。それは簡単Nengkanmingbaiする必要があります下の画像から。
我々は、従来のスタックの側面上部と称し、一方の側から読み書きできる所定のスタックが読み書きできます。他の側は、スタックの最下部と呼ばれる読み取りまたは書き込みません。私たちは、スタックのスタック要素の上部のみの後、および要素のスタックの最下部へのアクセスに、上記のチャートから見ることができます。
私たちは、Pythonのこのスタックデータ構造を達成するための配列、コメントを外し本当に唯一の少ない30行よりも、非常に単純であると言うことができ、コードを見てみましょうを使用しています。
class Stack(object):
def __init__(self, size_limit=None):
self._size_limit = size_limit
self.elements = []
self._size = 0
# 进栈,判断是否越界
def push(self, value):
if self._size_limit is not None and len(self.elements) > self._size_limit:
raise IndexError("Stack is full")
else:
self.elements.append(value)
self._size += 1
# 判断栈是否为空
def is_empty(self):
return self._size == 0
# 栈清空
def clear(self):
self.elements = []
self._size = 0
# 访问元素数量
def size(self):
return self._size
# 查询栈顶元素
def top(self):
return self.elements[-1]
# 弹出栈顶元素
def pop(self):
val = self.elements.pop()
self._size -= 1
return val
本質的に、一般的なスタックの実装はそう上記ほんの数の方法は、小さくてもよいです。スタック、トップ、そしてポップの間で言語のいくつかがマージされますので。アクセスがポップしなければならないことを意味は、ポップは非アクセスをサポートしていません。だから、スタックの実装ロジックは非常に簡単で、さらにエントリデータ構造のための技術的な内容、理想的には存在しないということができます。
もちろん、他の側から、また、より重要な通常の任意の場所で使用されるスタックであると比べて、スタックとそれほど重要なの原則を達成するために言うことができます。
アプリケーションのスタック
スタックは、最も広く使われているオペレーティングシステムであり、例えば、プログラムが実行のメソッドを呼び出したときに、内部コンパイラは、実際には、現在のコールスタックを記録する方法です。例えば、そのような方法は、Aに現在の呼であるAプロセスは、メソッドBと呼ば行った場合順番にBがCにメソッドを呼び出す場合、コンピュータは、スタックポインタ方法Bのシステムに保存されますその後、C.へのポインタを追加します 実行方法Cの端部は、次いでCがポップアップ表示されたときにスタックが空になるまで、コンピュータは、継続実行Bの前に、というようにC、B、になります。
方法Aは、自分自身を呼び出すのであれば、問題は、何が起こるのだろうか?
答えは、システムは、新しいスタックポインタを作成し、Aを再帰的に続けば、スタックポインタAを記入します、新しいコンピュータを作成することです......
上記のプロセスから、我々は2つのことを決定することができます。まず、我々は再帰的手続きの時間を書き、実際には、それが実行の内部コンパイラスタック形式です。我々は維持するために再帰的な無限ループを使用する場合は、2番目は、サイズ制限のためには、スタックので、スタックの深さが制限を超えた場合、それはエラーをSystemStackExceedます。外部を実行するためのオペレーティングシステムのメモリ制限に加えて、コンパイラは、最大の再帰の深さ制限があり、システムクラッシュをもたらす再帰無限ループを防止するので、それは、無限の再帰ありません。各言語のメカニズムがまったく同じではありませんが、一つのことは確かである、再帰の深さが制限され、我々は無制限再帰することはできません。
私たちのシステムが行う方法を大規模な再帰があるだろうということであれば質問は、ありますか?手動で行うマシンにメモリを追加することは可能ですか?
これは、コンピュータの最大の再帰の深さをテストしますフィールド上のACMプレーヤーは、多くの場合、最初の日のウォームアップマッチでの経験豊富な選手がvimのか、他のIDEの構成に加えて行われます発生する問題の一つであり、 。C ++では、アセンブリ言語によるオープン再帰の深さの制限を強制的にサポートされていますが、たとえそうであっても制限されており、その私の知る限り唯一のC ++は、他の言語のために、それを行うことができ、および再帰の深さのケースは十分ではありません開かれました、唯一の方法は、手動でビルドスタックシミュレーション再帰にあるがあります。
マニュアル再帰
多くの学生は、痛みの再帰を感じるかもしれないが、彼らは手動でシミュレート再帰のスタックを構築しようとした場合、あなたはより多くの苦痛であることがわかります。中間状態、およびプログラムを格納するための追加の変数にだけでなく、またにとって大きな課題です。
例で見てみましょう:
class Node:
def __init__(self, val):
self.val = val
# 左孩子
self.lchild = None
# 右孩子
self.rchild = None
if __name__ == "__main__":
# 建树
root = Node(0)
node1 = Node(1)
root.lchild = node1
node2 = Node(2)
root.rchild = node2
node3 = Node(3)
node1.lchild = node3
node4 = Node(4)
node1.rchild = node4
node5 = Node(5)
node2.rchild = node5
これは次のように描かれたシンプルなバイナリツリー、次のとおりです。
0
/ \
1 2
/ \ \
3 4 5
ここでは、我々はすべてのノウハウを予約限定、再帰的行きがけそれを使用せずに、スタックを通過したい、左のサブツリー、そして電流出力ノードを通過し、右部分木を横断する最初のものです。非常に便利な再帰的な書き込み、わずか数行:
def dfs(node):
if node is None:
return
dfs(node.lchild)
print(node.val)
dfs(node.rchild)
あなたは私が何をすべきか、再帰を使用しない場合は、それについて考えてみようか?あなたが本当に書き込みしようとすると、一見単純な問題は非常に複雑であるように思われるでしょう。我々は簡単に私たちは、スタック上に保存されたノード間で持っていますが、保存されたデータの外観のみ、想像することができます。問題の本質は、我々は彼らのスタックからノードを取得するとき、どのように我々はそれが何をすべきかを決定しますでしょうか?これは、出力が何をすべきか、右のノードを通過する必要があり、左ノードトラバーサルすべきですか?
これらの問題について慎重な分析や思考、我々は、彼らがすべての関連および再帰的なバックトラックしていることがわかります。
それらを再帰的に、我々はポップのスタックで、現在のノードのサブツリーを反復処理する場合、このノードを返します。例えば、その上の木は、ここで再帰的プロセスは、我々は2 1このノードが発生します。それは、出力1はありませんが、その理由は、左の部分木の、1に戻し、再び、3でその左部分木を、トラバースに行く最初の時間は、出力1意志、トラバースされています。左バックはバックトラックと呼ばれています。あなたは木が下がると考えるならば、このプロセスは少し川下りのようなものですが、また、上流、背中に翻訳まだかなり合理的です。
私たちが問題に戻る前に、すべての混乱した性質は、我々は現在、バックトラックの後の最初の会議や再会を経験しているものを判断できないノードに由来しています。そして、これは、我々はそれが何をしたいのかに関係しています。再帰的な実行は、再帰的なバックトラックの最後の呼び出しの後の位置に戻りますときに我々は、この問題を無視することができますので、プログラムは、場所と状況コードを記録しますので、もともと、それらを再帰的。今、私たちはもはや使用再帰ので、私たちは自分のためにノードの状態を判断する必要がありそう。
実際には非常に簡単です考え出し、我々は唯一のノードフィールドはノードのバックトラックが発生したかどうかを示すた状態を追加する必要があります。もちろん初めに、すべてのノードの状態はTrueです。
class Node:
def __init__(self, val):
self.val = val
self.lchild = None
self.rchild = None
self.flag = True
我々はデフォルトはtrue、それを初期化するときに我々Nodeクラスは、レコードとしてフラグを追加します。その後、我々は、これらのノードは、そのバックするので、すべての方法Falseにすべてのセットの左にプロセスにおいて遭遇する、長い左側のサブツリートラバーサルの左側に出て行くことがあるようとして、右、左の順にフラグをノードを非常に簡単なトラバースすでに始まっている、未来は戻って再び起こることはありません。右トラバーサル問題のバックトラックが存在しないので、無視することができ、見たいと思っているので、コードが先に出てきます。
# 使用我们自己刚刚创建的数据结构
stack = Stack()
# 插入根节点
stack.push(root)
while not stack.is_empty():
# 获取栈顶元素,也就是当前遍历的节点
tmp = stack.top()
# 如果不曾回溯过,并且左子树存在
while tmp.flag and tmp.lchild is not None:
# 回溯标记置为False
tmp.flag = False
# 栈顶push左孩子
stack.push(tmp.lchild)
# 往左遍历
tmp = tmp.lchild
# 弹出栈顶
tmp = stack.pop()
# 此时说明左节点已经遍历完了,输出
print(tmp.val)
# 往右遍历
if tmp.rchild is not None:
stack.push(tmp.rchild)
このコードは短いですが、単純ではない実際には、あなたは完全に再帰的な循環の深い理解の必要性を理解したいです。本当の問題で、典型的なシンプルな外観は容易ではないですが、私は個人的に思考の運動に加えて、この種の問題を好むもインタビュー、思考の候補、基本的にコード鮮明な画像をコントロールする能力に非常に適しています。実際のシーンが、将来は読んでいる限り、あなたが主張するように、再帰的な検索アルゴリズム上の他の記事を紹介しますここで、このような場面に遭遇していないので、学生は、心配しないで、理解していない、私たちはそれを読むと確信しています。
今日の記事では、それは、してください収穫された場合に、ということである懸念の簡単スキャンコードポイント今、あなたささいなことは私にとって重要です。