データ構造とアルゴリズム(1):基本的なデータ構造(アルゴリズムの概念、配列、リンクリスト、スタック、キュー)

アルゴリズムの概念、配列、リンク リスト、スタック、キュー

数値が 2 の N 乗かどうかを判断しますか?

N & (N-1) == 0  (N > 0)

計算問題:

アルゴリズム

アルゴリズムの概念

代表的なアルゴリズム: 高効率、低ストレージ、小さいメモリ使用量、小さい CPU 使用量、高速な計算速度

アルゴリズムの高効率と低ストレージ: メモリ + CPU

アルゴリズムを評価するための 2 つの指標

  • 時間計算量: プログラムの実行にかかる時間 O()
  • スペースの複雑さ: プログラムの実行に必要なメモリ OOM

時間の複雑さO(1,logn,n,nlogn,n^2,n^x)

  • 定数: O(1) 1 は定数であることを意味します。決定できるすべての数値には O(1) を使用します。O(10000)=>O(1)
  • 対数:O(logn)、O(nlogn)
  • 線形: O(N) n は未知である必要があります。n が既知の場合は O(1)
  • 線形対数: O(nlogn)
  • 平方: O(n^2)
  • N次方: O(n^n)

時間計算量を見つける方法

  • 悪いところがある
  • ネットワーク要求がある場合 (RPC、リモート呼び出し、分散、データベース)

テスト時のログの印刷

ここに画像の説明を挿入

O(1) > O(logn) > O(n) > O(nlogn) > O(n^2) > O(n^n)

O(1) に近づくほど、時間計算量は低くなります

定数: O(1)

int a = 1; // 1次O(1)
for(int i = 0; i < 3; i++) {
    
     //这里运行4次
  a = a + 1; //这里运行3次
}

対数:O(logn)、O(nlogn)

//对数 2^x=n  x就是我们运行的次数 => 对数 x=log2(n) = log2n = 计算机忽略常数 => logn => O(logn)
int n = Integer.MAX_VALUE;
int i = 1;

//O(logn)
while (i <= n) {
    
    
    i = i * 2;
}

//O(nlogn)
for (int j = 0; j < n; j++) {
    
    
    while (i <= n) {
    
    
        i = i * 3;
    }
}

リニア: O(N)

//线性: O(N) n一定是未知的; 如果n是已知的O(1)
for (i = 0; i < n; i++) {
    
    
    a = a + 1;
}

平方: O(n^2)

for (i = 0; i < n; i++) {
    
    
    for (int j = 0; j < n; j++) {
    
    
        a = a + 1; // O(n^2)  n*(n+1)/2 => O(n^2) = 1+2+3+4+..+n=n*(n+1)/2
    }
}

チューニング

リストソート バブルソート < クイックソート マージソート ヒープソート

クラシック: リンク リスト、ソート アルゴリズム、バイナリ ツリー、赤黒ツリー、B ツリー、B+ ツリー

上級: 数論、グラフ理論

データ構造

配列

考えるための質問:

  • 質問 1: この国の 14 億人の年齢データ (0 ~ 180) を含むファイルを渡して、各年齢の人数を数えてみてください。
    単一マシンのメモリを 2G + 2G に制限し、マップなどの既製のコンテナーを使用しないでください。

int age[] = new int[180];
a[0]++; // 0 は 0 歳を意味します

配列の添字を使用すると、配列内の特定のデータをランダムに配置することもできます。

  • 質問 2: 値の添字が 0 から始まり、配列の特徴が連続したメモリアドレスであるのはなぜですか

int arr[] = 新しい int[5];

適用されるメモリアドレスは例: 10001、10002、10003、10004、10005

保存数据:a[0] => 10001 ===> 10001 + 0
保存数据:a[1] => 10002 ===> 10001 + 1
保存数据:a[2] => 10003 ===> 10001 + 2
保存数据:a[3] => 10004 ===> 10001 + 3
保存数据:a[4] => 10005 ===> 10001 + 4
  • 質問 3: 2 次元配列のメモリ アドレスは何ですか?
1 2 3
4 5 6  =>  1 2 3 4 5 6 => i*n + j (i是一维数组的长度、j是在列的位置) => 4 => 1*3 + 0 = 3

ArrayList と配列のどちらを選択するか

  • ArrayList は JDK によってパッケージ化されているため、展開する必要はありません
  • 配列の削除と追加は遅い O(n)、変更と取得は速い O(1)
  • ランダムアクセス
  • 添字

どのように選ぶか?

  • データサイズがわからない場合はArrayListを選択してください
  • データのサイズがわかっていて、選択配列のパフォーマンスが非常に気になる場合は、範囲外 (先頭と末尾) に注意する必要があります。

Javaメモリはヒープメモリとスタックメモリに分けられます

ヒープ メモリ: 新しいオブジェクトと配列を格納する
スタック メモリ: 変数を参照する

ヒープとスタックの両方がデータの保存に使用されます

スタックの違い:

  • スタックのほうが速い
  • スタックのメモリ データは共有でき、主にいくつかの基本的なデータ型を保存します。int a = 3; 在栈中创建变量a, 然后给a赋值,先不会创建一个3而是先在栈中找有没有3
String s1 = "ja";
String s2 = "va";
String s3 = "java";
String s4 = s1 + s2; // java 里面重装了+, 其实调用了 stringBuild, 会new对象
System.out.println(s3 == s4); //false
System.out.println(s3.equals(s4)); //true

リンクされたリスト

考えるための質問:

  1. LRU キャッシュ削除アルゴリズム (リンク リスト) を設計する方法
  2. ジョセフ問題 (循環連結リスト): N 人が円を形成し、最初の人から数え始め、M 人目が削除され、最後の人が残り、残りが殺されます。たとえば、N=6、M=5 の場合、殺される順序は 5、4、6、2、3、1 です。

リンクリスト機能

  • 連続したメモリ空間は必要ありません
  • ポインタ参照があります
  • 一般的なリンク リスト構造: シングル リンク リスト、ダブル リンク リスト、循環リンク リスト

単一リンクリスト LinkList

クエリ O(n)
挿入 削除 O(1)

二重リンクリスト

B+ ツリー Mysql リーフ ノードは二重リンク リストであり、
ジャンプ テーブルは Mysql B+ ツリーに非常に似ています

循環リンクリスト

循環リンク リストは、特別な種類の単一リンク リストです。循環リンク リストと単一リンク リストの唯一の違いは、末尾ノードです。単一リンク リストの末尾ノードは空のアドレスです。循環リンク リストの末尾ノードは次のとおり
です
。ヘッドノード。

リンクリストと配列の比較

お問い合わせ

配列

  • ○(1)

リンクされたリスト

  • の上)

挿入/削除

配列の挿入

  • テールO(1)
  • 正面から)

リンクリストの挿入

  • 頭O(1)
  • テールO(1)
  • 中級者O(1*2)

重要な違い:

  1. 配列はシンプルで使いやすく、連続したメモリ空間を使用して実装されており、CPU のキャッシュ機構を利用して配列内のデータを事前に読み込むことができるため、アクセス効率が高くなります。
  2. リンクされたリストはメモリに継続的に保存されないため、CPU キャッシュに優しくなく、効果的に先を読む方法がありません。
  3. 配列の欠点は、そのサイズが固定されており、一度宣言すると連続したメモリ空間全体を占有してしまうことです。宣言された配列が大きすぎる場合、システムに十分な連続メモリ領域が割り当てられず、
    「メモリ不足」が発生する可能性があります。宣言された配列が小さすぎる場合は、十分ではない可能性があります。
  4. 動的拡張: 配列はより大きなメモリ空間に適用する必要があり、元の配列をそこにコピーするのは非常に時間がかかります。リンクされたリスト自体にはサイズ制限がなく、動的拡張を自然にサポートします。

スタック - 後入れ先出し - LILO

考えるための質問:

  1. 括弧一致関数を設計するにはどうすればよいですか? スタック
  2. ブラウザの進む機能と戻る機能を設計するにはどうすればよいですか? (2 つのスタック、1 つは前方、もう 1 つは後方)
  3. 3+2*5-3などの四則演算を実装するにはどうすればよいですか? (2 つのスタック、1 つの数字、1 つの記号)
  4. コード関数呼び出しシーケンス

スタック機能

スタックは、スタックの最上部と呼ばれるテーブルの端でのみ挿入および削除操作を行うように制限された線形リストであり、もう一方の端はスタックの最下部となります。

新しい要素をスタックに挿入することをプッシュ、プッシュ、プッシュと呼び、
要素を削除することをポップおよびスタック解除と呼びます。

スタックは実際には特殊なリンク リストまたは配列です。配列リンク リストは公開されるインターフェイスが多すぎるため、エラーが発生しやすくなります。

public class KuoHaoStack {
    
    
    public static boolean isOk(String s) {
    
    
        MyStack<Character> brackets = new ArrayStack<>(20);
        char c[] = s.toCharArray();
        Character top = null;
        for (char x : c) {
    
    
            switch (x) {
    
    
                case '{':
                case '(':
                case '[':
                    brackets.push(x);
                    break;
                case '}':
                    top = brackets.pop();
                    if (top == null) return false;
                    if('{' == top) {
    
    
                        break;
                    } else {
    
    
                        return false;
                    }
                case ')':
                    top = brackets.pop();
                    if (top == null) return false;
                    if('(' == top) {
    
    
                        break;
                    } else {
    
    
                        return false;
                    }
                case ']':
                    top = brackets.pop();
                    if (top == null) return false;
                    if('[' == top) {
    
    
                        break;
                    } else {
    
    
                        return false;
                    }
                default:
                    break;
            }
        }
        return brackets.isEmpty();
    }

    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
    
    
            System.out.println("S的输出结果:" + isOk(scanner.next()));
        }
    }
}

キュー – 先入れ先出し – FIFO

考える質問

  • スレッドプール内のタスクがいっぱいになると、この時点で新しいタスクが来ますが、スレッドプールはそれをどのように処理するのでしょうか? 具体的な戦略とは何でしょうか?これらの戦略はどのように実行されるのでしょうか?
  1. キュー: ブロックキュー。空いているときに取ってください、テイクアンドプットではないですか、公平であれば先入れ先出しでなければなりません。これが今日話しているキューです。現時点では 2 つの方法があり、1 つは無限キューです。(リンク リスト。使用しないでください。LinkedBlockingQueue、JDK)、制限されている (配列によって実装される) 別の種類があり、開いたスペースのサイズのみを処理し、それ以上をスローし続けます。Integer.MAX=?2^32-1=21 億を超えていますが、このキューのサイズに注意してください。小さくしないでください。足りなければ、大きすぎると無駄になってしまいます。一部の小規模システムでは、要求されるデータの量がそれほど多くないため、使用できることがわかっています。
  2. 廃棄:処理せず、そのまま捨ててください。

キューは特別な線形テーブルであり、テーブルのフロントエンドでの削除操作とテーブルのバックエンドでの挿入操作のみを許可するという点で特殊です。

キューの分類

  1. シーケンシャル (一方向) キュー: (キュー) は一方の端でのみデータを挿入し、もう一方の端でデータを削除できます。
    ここに画像の説明を挿入
  2. 循環 (双方向) キュー (Deque): 各エンドでデータの挿入および削除操作を実行して、キューが
    いっぱいかどうかを判断できます。
  • 方法 1: サイズを追加する
  • 方法 2: (尾部 + 1)%n == 頭

ここに画像の説明を挿入

public class CircleArrayQueue<Item> {
    
    

    private Item data[];
    private int head = 0;
    private int tail = 0;
    private int n = 0; //数组最大空间
    private int size; //当前队列已存个数

    public CircleArrayQueue(int cap) {
    
    
        data = (Item[]) new Object[cap];
        n = cap;
    }

    public void push(Item item) {
    
    
        if ((tail + 1)%n == head) return; //关键点

        data[tail] = item;
        tail = (tail + 1) % n; //关键点
    }

    public Item pop() {
    
    
        if (isEmpty()) return null;

        Item item = data[head];
        head = (head + 1) % n;
        return item;
    }

    public boolean isEmpty() {
    
    
        return head == tail;
    }
}

おすすめ

転載: blog.csdn.net/menxu_work/article/details/129048556