二重接続リスト

66496 ワード

*この記事は2022年1月から2月にかけて行われた研究の記事です.

22.01.10円形接続リストのクリーンアップ


リング接続リストのフィーチャー

  • の最後のノードへのリンクは、最初のノードを指します.すべてのノードの接続形式.つまり、すべてのノードのリンクはNULLではありません.*図にはHeadとTailが表示されていますが、私はHeadをTailの位置に置きます.
  • ヘッダポインタが最後のノードを指すように構成されている場合、最初のノードも分かりやすく、最後のノードも分かりやすい.
  • ノードが1の円形リストには、独自のリンクがあります.
  • リング接続リストに関連する演算

  • ノードの挿入
  • 👉 원형 연결리스트의 **중간 노드**로 삽입하는 경우
    
    
    ```cpp
    void insert_node_middle(Node* new_node, Node*pre) {
    	if (Head == NULL) {
    		new_node->link = new_node;
    		Head = new_node;
    	}
    	else if (pre == NULL) {
    		new_node->link = Head->link;
    		Head = new_node;
    	}
    	else {
    		new_node->link = pre->link;
    		pre->link = new_node;
    		if (Head == pre) {
    			Head = new_node;
    		}
    	}
    }
    ```
    
    
    👉 원형 연결리스트의 **첫 노드**로 삽입하는 경우
    
    
    
    
    ```cpp
    void insert_node_front(Node*new_node) {
    	if (Head == NULL) {
    		new_node->link = new_node;
    		Head = new_node;
    	}
    	else {
    		new_node->link = Head->link;
    		Head->link = new_node;
    	}
    }
    ```
    
    
    👉 원형 연결리스트의 **마지막 노드**로 삽입하는 경우
    
    
    
    
    ```cpp
    void insert_node_rear(Node* new_node) {
    	if (Head == NULL) {
    		new_node->link = new_node;
    		Head = new_node;
    	}
    	else {
    		new_node->link = Head->link;
    		Head->link = new_node;
    		Head = new_node;
    	}
    }
    ```
  • ノードの削除
  • 👉 선행노드가 있는 경우 노드 삭제
    
    
    
    ```cpp
    void removed_node(Node* pre) {
    	Node* removed;
    	if (Head == NULL || Head->link == Head)Head = NULL;
    	else {
    		removed = pre->link;
    		pre->link = removed->link;//pre->link=pre->link->link도 가능함
    		if (Head == removed)Head = pre;
    	}
    }
    ```
    
    
    👉 특정 값을 가진 데이터 삭제
    
    
    
    ```cpp
    void removed_node_key(int key) {
    	if (Head == NULL)return;
    	else if ( (Head->link == Head) && (Head->data = key) ) {
    		Head = NULL;
    	}
    	else {
    		Node* pre = Head;
    		do{
    			if (pre->link->data== key) {
    				Node* removed = pre->link; 
    				/*왜 새로운 노드를 굳이 추가하냐면 removed로 설정된 값이 계속 변하기 때문에 임의의 노드에 저장하는 것*/
    				pre->link = removed->link;
    				if (Head == removed) Head = pre;
    				return;
    			}
    			pre = pre->link;
    		} while (pre != Head);
    	}
    }
    ```
  • ノード出力
    **//do-while문 사용하는 이유에 대해 생각해보기**
    void print_node() {
    	if (Head == NULL) {
    		cout << "empty node" << endl;
    	}
    	else {
    		Node* list = Head;
    		do {
    			cout << list->link->data << endl;
    			list = list->link;
    		} while (list != Head);
    	}
    }
  • 完全コード
    **//원형연결리스트 복습**
    #include<iostream>
    using namespace std;
    
    class Node {
    public:
    	int data;
    	Node* link;
    };
    
    Node* Head;//리스트의 마지막노드를 가리킴
    
    **/*선행노드를 아는 경우*/**
    void insert_node_A(Node*pre,Node*new_node) {
    	if (Head == NULL) {
    		new_node->link = new_node;
    		Head = new_node;
    	}
    	else {
    		new_node->link = pre->link;
    		pre->link = new_node;
    		if (pre == Head) {
    			Head = new_node;
    		}
    	}
    }
    **/*선행노드를 모르는 경우(마지막)*/**
    void insert_node_B(Node* new_node) {
    	if (Head == NULL) {
    		new_node->link = new_node;
    		Head = new_node;
    	}
    	else {
    		new_node->link = Head->link;
    		Head->link = new_node;
    		Head = new_node;
    	}
    }
    **/*선행노드를 모르는 경우(처음)*/**
    void insert_node_C(Node* new_node) {
    	if (Head == NULL) {
    		new_node->link = new_node;
    		Head = new_node;
    	}
    	else {
    		new_node->link = Head->link;
    		Head->link = new_node;
    	}
    }
    **/*특정 값의 노드 삭제*/**
    void delete_node(int key) {
    	if (Head == NULL)return;
    	else if (Head->link==Head&&Head->data==key) {
    		Head = NULL;
    	}
    	else {
    		Node* list = Head;
    		do {
    			if (list->link->data == key) {
    				Node* removed = list->link;
    				list->link = removed->link;
    				if (Head==removed) {
    					Head = list;
    				}
    				return;
    			}
    			list = list->link;//논리적오류 이유: 반복 조건을 if문 안에 넣어버림.^^;
    		} while (list != Head);
    	}
    }
    **/*원형연결리스트의 출력*/**
    void print_node() {
    	if (Head == NULL) {
    		cout << "empty node" << endl;
    	}
    	else {
    		Node* list = Head;
    		do {
    			cout << list->link->data << endl;
    			list = list->link;
    		} while (list != Head);
    	}
    }
    
    **void main() {**
    	Head = NULL;
    	int answer;
    	int del;
    	for (int i = 0; i < 3; i++) {
    		cout << "노드의 데이터를 입력하세요: ";
    		cin >> answer;
    		Node* new_node = new Node;
    		new_node->data = answer;
    		new_node->link = NULL;
    		insert_node_B(new_node);
    	}
    	print_node();
    	cout << "삭제할 데이터를 입력하세요: ";
    	cin >> del;
    	delete_node(del);
    	print_node();
    }
  • 22.01.11デュアル接続リストの整理(22.01.12追加)


    二重接続リストのフィーチャー

  • 1ノードは、前のノードと後のノードへの2つのリンクを有する.したがって,双方向探索を行い,より簡単な演算で挿入/削除などを行うことができる.しかし、実施が複雑であるという欠点もある.
  • は、二重接続リスト+円形リスト+ヘッダノードの混合形式を採用している.ヘッダーとリンク値がNULLの場合は削除します.コードを書くときは、コードをより簡単に書くことができます.
  • ヘッダノードとは?データがなく、挿入/削除コードを簡単に作成するためのノードです.ヘッドポインタと他のノード!!(ただし、ヘッドポインタは常にヘッドノードを指す)
  • .
  • をまとめると、2つの接続リストを使いやすいように変換する利点は、NULL値を持つポインタがなく、前後の検索が可能であることです.
  • デュアルコネクションリスト関連演算

  • ノードの構成
    class Node{
    public:
    	int data;
    	Node*llink;
    	Node*rlink;
    	**//생성자**
    	Node() {
    		llink = rlink = NULL;
    	}
    	Node(int value) {
    		data = value;
    		llink = rlink = NULL;
    	}
    };
  • ノードの挿入

  • ノード
    **/*특정 데이터를 가진 노드 삭제*/**
    void delete_node(int key) {
    	for (Node* list = Head->rlink; list != Head; list = list->rlink) {
    		if (list->data == key) {
    			list->llink->rlink = list->rlink;
    			list->rlink->llink = list->llink;
    			break;
    		}
    	}
    }
  • を削除する.
  • ノード出力
    void print_node() {
    	cout << "[리스트의 내용 출력]" << endl;
    	for (Node* list = Head->rlink; list !=Head; list = list->rlink){
    		cout << list->data << endl;
    	}
    }
  • 完全コード
    **//이중연결리스트 구현
    #include<iostream>
    using namespace std;
    
    //이중연결리스트(원형리스트+헤드노드)
    class Node {
    public:
    	int data;
    	Node* llink;
    	Node* rlink;
    };
    
    Node* Head=NULL;
    
    /*선행노드를 아는 경우 노드삽입*/
    void insert_node_A(Node*pre,Node*new_node) {
    	new_node->rlink = pre->llink;
    	new_node->llink = pre;
    	pre->rlink->llink = new_node;
    	pre->rlink = new_node;
    }
    /*선행노드 모르는 경우 첫 노드(헤드노드 제외) 삽입*/
    void insert_node_B(Node* new_node) {
    	new_node->llink = Head;
    	new_node->rlink = Head->rlink;
    	Head->rlink->llink = new_node;
    	Head->rlink = new_node;
    }
    /*선행노드 모르는 경우 리스트 마지막에 노드에 삽입*/
    void insert_node_C(Node* new_node) {
    	new_node->rlink = Head;
    	new_node->llink = Head->llink;
    	Head->llink->rlink = new_node;
    	Head->llink = new_node;
    }
    
    /*특정 데이터를 가진 노드 삭제*/
    void delete_node(int key) {
    	for (Node* list = Head->rlink; list != Head; list = list->rlink) {
    		if (list->data == key) {
    			list->llink->rlink = list->rlink;
    			list->rlink->llink = list->llink;
    			break;
    		}
    	}
    }
    
    /*노드 출력*/
    void print_node() {
    	cout << "[리스트의 내용 출력]" << endl;
    	for (Node* list = Head->rlink; list !=Head; list = list->rlink){
    		cout << list->data << endl;
    	}
    }
    
    void main() {
    	//헤드노드 생성
    	Head = new Node;
    	Head->data = NULL;
    	Head->llink = Head;
    	Head->rlink = Head;
    	int ans;
    	int data;
    	for (int i = 0; i < 3; i++) {
    		cout << "데이터의 값을 입력해주세요: ";
    		cin >> ans;
    		Node* new_node = new Node;
    		new_node->data = ans;
    		new_node->rlink = NULL;
    		new_node->llink = NULL;
    		insert_node_C(new_node);
    	}
    	print_node();
    
    	cout << "삭제할 값: ";
    	cin >> data;
    	delete_node(data);
    	print_node();
    }**
  • プログラマブルの第1段階はクレーンの問題を解決します


  • 注x(論理エラー)
    #include <string>
    #include <vector>
    #include <cmath>
    /*
    1. moves가 갖고있는 값을 선택함(열)
    2. 같은 열 내에서 행의 값이 0이 아닌 가장 큰 인덱스를 찾음.
    3. 해당 인덱스의 값을 can에 넣고 해당 인덱스의 값은 0으로 바꿈. 
    4. 반복을 수행하는 과정에서 연속적으로 중복인 원소가 존재하면 해당 인덱스 두개를 삭제해야 함. 
    5. 삭제할 때 답에 2씩 더함.
    */
    using namespace std;
    
    int solution(vector<vector<int>> board, vector<int> moves) {
        int answer = 0;
        vector<int> can;
        
        //board의 제곱근 구하여 N의 값을 찾는다
        int idx=sqrt(board.size());
    
        //int j: 열(moves가 갖고있는 값)
        //int i: 상하로 행 탐색(4-3-2-1-0 원소값 중 0이 아닌 값을 발견하면 리턴 종료)
        for(int j=0;j<moves.size();j++){        
            for(int i=idx-1;i==0;i--){            
                if((board[i][moves[j]-1])!=0){
                    can.push_back(board[i][moves[j]-1]);
                    //한번 크레인에 선택된 원소는 0으로 처리
                    board[i][moves[j]-1]=0; 
                }
            }
        }
        if(can.size()>=2){
            for(int k=can.size()-1;k==0;k--){
                if(can[k]==can[k-1]){
                    answer+=2;
                    can.erase(can.begin()+k , can.begin()+k-1);
                }
            }
        }
        
        return answer;
        }

  • コメント後の変更
    #include <string>
    #include <vector>
    /*
    1. moves가 갖고있는 값을 선택함(열)
    2. 같은 열 내에서 행의 값이 0이 아닌 가장 큰 인덱스를 찾음.
    3. 해당 인덱스의 값을 can에 넣고 해당 인덱스의 값은 0으로 바꿈. 
    4. 반복을 수행하는 과정에서 연속적으로 중복인 원소가 존재하면 해당 인덱스 두개를 삭제해야 함. 
    5. 삭제할 때 답에 2씩 더함.
    */
    using namespace std;
    
    int solution(vector<vector<int>> board, vector<int> moves) {
        int answer = 0;
        vector<int> can;
    
        //int j: 열(moves가 갖고있는 값)
        //int i: 상하로 행 탐색(4-3-2-1-0 원소값 중 0이 아닌 값을 발견하면 리턴 종료)
        for(int j=0;j<moves.size();j++){        
            for(int i=0;i<board[0].size();i++){            
                if((board[i][moves[j]-1])!=0){
                    can.push_back(board[i][moves[j]-1]);
                    //한번 크레인에 선택된 원소는 0으로 처리
                    board[i][moves[j]-1]=0; 
                    if(can.size()>=2 && can[can.size()-1]==can[can.size()-2]){
                        can.pop_back();
                        can.pop_back();
                        answer+=2;
                    }
    						break;
                }
            }
        }
        
        return answer;
       }

  • 関連STL*のクリーンアップ
  • C++ STL

    Vectorバー


  • VectorはC++を用いて動的アレイ構造を実現した.上部から挿入と削除を開始し、Queueと理解します.

  • 動的にサイズを変更し、メモリが連続し、配列のサイズを自動的に調整し、オブジェクトを柔軟に追加および削除できます.

  • 中間データを削除することもできます.ベクトルのerase関数を通過できます.ただし、削除が頻繁に発生する場合は、ベクトルではなく接続リストを使用することをお勧めします.

    Vectorの宣言方法



  • #includeヘッダファイルを含める必要があります.

  • vectorname; と宣言した.
    1) ベクトル サイズは不明です.
    ベクトル<変数タイプ>名;
    vector<int> v;
    2)Vectorのサイズを決定します.
    ベクトル<変数タイプ>名前(サイズ);
    デフォルト値が0の寸法が10のベクトルvを宣言します.次の例に示します.
    vector<int> v(10);
    vector<string> v2(5);
    3)Vectorのサイズを決定し、データを初期化する.
    ベクトル<変数タイプ>名(サイズ、初期化定数);
    1に初期化する場合は、以下のように1と宣言します.
    vector<int> v(10,1); 

    ベクトルメンバー関数

    /*아래의 설명은 벡터를 다음과 같이 선언했다고 가정함*/
    vector<int>v;
    <1.要素アクセス>
    1) v[idx]
    idxの2番目の要素をv[idx]として参照します.
    2) v.at(idx)
    ベクトルvのidxの2番目の要素を参照します.
    3) v.front(), v.back()
    v.front():Vectorの最初の要素を参照してください.
    v.back():Vectorの最後の要素を参照してください.
    4) v.begin(), v.end()
    v.begin():反復器を使用してアクセスするときのベクトルの 最初のデータ位置を指します.
    v.end():反復器を使用してアクセスするときのベクトルの 最後のデータの場所を指定します.
    <2.挿入/削除>
    1)v.push back(データ);
    ベクトルvデータ型に適合するデータ(整数、文字列、文字など)を末尾に挿入します.
    2) v.pop_back();
    ベクトルvの端点データを削除します. 次はpush back、pop backを使用する例です!
    vector<int> v;
    v.push_back(10);
    v.push_back(20);
    v.pop_back();
    3)v.insert(データ位置、データ);
    ベクトルvの所望の位置(2)にデータ(3)を挿入する場合は、v.insert(2,3)と宣言する.
    vector<int> v;
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.insert(2,40);
    4) v.erase(iter)
    要素位置にiterを反復してアクセスし、ベクトルvのデータを削除します.
    v.begin()の場所のデータを削除するには、次の手順に従います.
    vector<int> v;
    v.push_back(10);//0
    v.push_back(20);//1
    v.push_back(30);//2
    auto iter = v.begin();
    v.erase(iter); //v.erase(v.begin())
    <3.寸法関数>
    1) v.size()   : 現在のベクトルvの要素数(サイズ)を返します.
    2) v.capacity() : 指定したベクトルの要素数(サイズ)を返します.
    3) v.resize(n) , v.resize(n,10)
    v.resize(n) : ベクトルを元のサイズからNサイズに変更します.
    v.resize(n,10):ベクトルをサイズNに変更し、データを10に初期化します.
    4) v.empty() ベクトルvが空かどうか.現在空の場合はTrue、空でない場合はFalseを返します.
    <4.for文、反復器を使用してアクセスするVector>
    1)for文:インデックスベースの要素アクセス
    要素には、繰り返し文を宣言してv[i]形式でアクセスできます.
    vector<int> v;
    
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);
    
    for(int i=0;i<v.size();i++){
    	cout << v[i] << endl;
    }
    出力結果
    10 20 30 40は、ベクトルにデータが挿入される順に出力されることがわかる.
    10
    20
    30
    40
    2)奇形発生器による元素への接近

  • 範囲ベースの繰返し文によるベクトルv要素の出力

  • v.begin()からv.end()への要素を反復器で出力
    vector<int> v;
    
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);
    
    for (auto iter : v) {
    	cout << iter << endl;
    }
    
    for (auto iter = v.begin(); iter < v.end() ; iter++ ) {
    	cout << *iter << endl;
    }
    出力結果
    10 20 30 40は、ベクトルにデータが挿入される順に出力されることがわかる.
    10
    20
    30
    40
    10
    20
    30
    40
    3)ベクトル端点の要素出力、ベクトルが空になるまで
    vector<int> v;
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);
    while (!v.empty()) {
    	cout << v.back() << endl;
    	v.pop_back();
    }
    出力結果
    出力されたデータは、ベクトルに挿入されたデータとは逆の40302010であることがわかる.
    40
    30
    20
    10
  • C++ STL

    スタックとは


  • 代表的なLIFO構造.ジェルが最後に入れたデータは最初に流出します.強化時に強化に失敗すると前の段階に戻ります

    スタックの宣言方法



  • #includeヘッダファイルを含める必要があります.

  • stackname; と宣言した.
    #include<stack>
    stack<int>stack;

    stackメンバー関数


    1)スタックへのデータの追加
    スタック名.Push(データ)  フォーマットでデータを追加します.
    stack.push(element)
    2)スタックからのデータの削除
    スタック名.スタック内のtopデータをpop(データ)として削除します.
    stack.pop()
    3)スタック上部(上部、上部)のデータを返す
    スタック名.トップレベルのデータをtop()として返します.
    stack.top()
    4)返却スタックのサイズ
    スタック名.スタックの現在の寸法をsize()形式で返します.
    stack.size()
    5)スタックが空であるかどうかを確認する
    スタック名.スタックが空であるかどうかを確認します().
    stack.empty()
    6)スタックSWAP  : 2つのスタックの内容を置換
    スタック1とスタック2の2つのスタックの内容を変更する場合は、内蔵のswap関数を使用します.
    2つのスタックの内容をswap(スタック1名、スタック2名)と置き換えます.
    swap(stack1 , stack2)

  • ポスト

  • ベクトル関数にはpush back()のほかにpop back()もありますが、これは非常に重要で当然です...!概念を理解した.△stlを整理する気がします.問題に書いてあるstlからゆっくり整理しましょう.

  • pop関数を知る前にerase関数によって実現された.この関数は、最後の要素ではなく、指定したインデックスを消去できるため、非常に役立ちます.しかし、簡単な演算には使いにくい.(パラメータでbegin()またはend()を使用して演算する必要がある場合)→erase関数は、最初から接続リストとしてデータ型を整理することが望ましい.

  • 世云的なのにいつも実行中に间违っている理由を考えて私とあまり差のないコードは、よく動作している人のコードと私のコードを比較します.
  • 関数に括弧を付けないエラーが発生しました.^^;;;;;z
  • iを使用するfor文では、もともとは加減式で書かれていたが、加減式で書かれたとき、私が書いたアルゴリズムは正常に動作していた.つまり、逆の手配です.理由は.
  • 二次元配列板の構成を間違えたことに気づいたばかりです.もちろん下から上の順にインデックスを拡大するので、勝手に推測します.ただし、指定された配列を考慮すると、インデックスは上から下への順序が大きくなります.与えられたboard配列には上から下までゼロの要素があるからです.そこで、確信して書いたコードをつかむのに数時間かかりました.
  • このような基本的な間違いを犯さないためには、まず配列の基本から固めましょう.