Java データ構造とアルゴリズム分析 - 表

Java データ構造とアルゴリズム分析 - 表

このセクションでは、一般的でよく使用されるデータ構造であるテーブルについて説明します。

テーブルとは何かを簡単に説明すると、順序よく並べられた要素の集合がテーブルであると言えます。

表の概要

抽象データ型は、いくつかのオブジェクトと一連の操作の組み合わせです。

1. 定義:

線形リストは線形構造であり、n ≥ 0 個のノードの有限シーケンスです。各ノードには、先行ノードはないが後続ノードが 1 つある開始ノードが 1 つだけあり、後続ノードはないが先行ノードが 1 つある終端ノードが 1 つだけあり、その他のすべてのノードには先行ノードと後続ノードが 1 つずつあります。

2. 特性/性質

  • 1) セットには要素が1つだけ存在する必要がある
  • 2) セットには***要素が1つだけ存在する必要があります
  • 3) 最後の要素を除くすべての要素には、固有の後継要素がある
  • 4) 最初の要素を除いて、すべての要素には固有の先行要素がある

上の図では、a1 は a2 の前身、ai+1 は ai の後身、a1 には前身がなく、an には後身がなく、n は線形テーブルの長さです。n==0 の場合、線形テーブルは空です。下の図は標準的な線形テーブルです。

リニア テーブルは次のカテゴリに分類されます。

順次格納線形テーブル

順次格納される線形テーブルでは、格納場所が連続しており、各要素のアドレスを簡単に計算できます。

各要素が C 個のストレージ ユニットを占有する場合、Loc(An) = Loc(An-1) + C となり、Loc(An) = Loc(A1)+(i-1)*C となります。

利点:高速クエリ

デメリット:挿入と削除の効率が遅い

次の図は、遅い挿入と削除の特徴を鮮明に示しています。

テーブルのシンプルな配列実装 典型的な線形順次ストレージ方法は配列です。テーブル上のすべての操作は、配列を使用して実装できます。配列は固定サイズで作成されますが、必要に応じて容量の 2 倍の別の配列を作成することもできます。以下は拡張の疑似コードです。

  1. int [] aar = 新しいint [10];
  2. //aarを展開
  3. int [] newArr = 新しいint [aar.length * 2];
  4. ( int i = 0; i < aar.length; i++) {
  5. aar[i] を新しいArr[i] に変換します。
  6. }
  7. aar = 新しいArr;

配列の実装により、printList は線形時間で実行できますが、findKth (特定の位置にある要素を返す) には一定の時間がかかります。

最悪のケースでは、位置 0 に要素を挿入するには、配列内のすべての要素を 1 つ後ろへ移動する必要があり、要素を削除するには、すべての要素を 1 つ前へ移動する必要があります。どちらの場合も複雑さは O(n) です。平均すると、両方の操作は配列の要素の半分を移動する必要があるため、線形時間がかかりますが、挿入と削除の両方が配列の末尾で発生する場合、挿入と削除の両方にかかる時間は O(1) だけです。

テーブルの先頭で頻繁に挿入や削除が行われる場合は、リンク リストの方が適しています。

チェーン収納リニアテーブル

線形リストのチェーン ストレージ構造の特徴は、任意のストレージ ユニットのセットを使用して線形リストのデータ要素を格納することです。このストレージ ユニットのセットは連続または不連続にすることができます。

利点:配列と比較して、削除と挿入が効率的です

デメリット:配列と比較するとクエリ効率が低い

挿入操作を実行するには、次のコードだけが必要です。

  1. s->= p-> 
  2. p-= s;

削除操作を実行するには、次のコードのみが必要です。

  1. p->= p->次- > 

循環リンク リストは、単一リンク リストの終端ノードのポインターの端をヌル ポインターから先頭ノードに変更し、単一リンク リスト全体がループを形成するようにします。このような先頭から末尾まで接続された単一リンク リストは、単一循環リンク リスト、または略して循環リンク リストと呼ばれます。

二重循環リンクリスト

双方向循環リンク リストは、各ノードにその前のノードを指すポインター フィールドがある単方向循環リンク リストです。

空の二重循環リンクリストの場合

二重リンクリストへの挿入

Java コレクション API のテーブル

1. IteratorIterator インターフェースの考え方は、Iterator メソッドを通じて、各コレクションが Iterator インターフェースを実装するオブジェクトを作成してクライアントに返し、オブジェクト内に現在の位置の概念を保存します。

  1. パブリックインターフェースIterator<E> {  
  2. ブール型hasNext();  
  3. E();  
  4. デフォルトvoid削除() {
  5. 新しい UnsupportedOperationException( "remove" ) をスローします。
  6. }  
  7. }

Iterator のメソッドは制限されているため、Collection を走査する以外の操作に Iterator を使用することは困難です。 Iterator には remove() メソッドも含まれています。このメソッドの目的は、next()*** によって返された項目を削除することです (next() を再度呼び出すまで、remove() を再度呼び出すことはできません)。

反復処理されるコレクションの構造が変更された場合 (つまり、コレクションに対して追加、削除、またはクリアが使用された場合)、反復子は有効ではなくなります (その後反復子を使用すると ConcurrentModificationException がスローされます)。反復子が次の項目として項目を提供する準備をし、その後その項目が削除されるのを避けるために、反復子はすぐに必要なときにのみ取得する必要があります。ただし、イテレータが独自の削除メソッドを呼び出す場合、イテレータは引き続き有効です。

2. リストインターフェース

  1. パブリックインターフェースList<E>はCollection<E>を拡張します{  
  2. 整数 サイズ();  
  3. ブール値 isEmpty();  
  4. イテレータ<E> iterator();
  5.   オブジェクト[] toArray();  
  6. <T> T[] toArray(T[] a);  
  7. ブール値add (E e);  
  8. ブール値削除(オブジェクトo);  
  9. ブール値 containsAll(コレクション<?> c);  
  10. ブール値 addAll(コレクション<? extends E> c);  
  11. ブール値addAll( int  インデックス、コレクション<? extends E> c);  
  12. ブール値removeAll(コレクション<?> c);  
  13. ブール値のretainAll(コレクション<?> c);  
  14. void をクリアします。  
  15. ブール値は(オブジェクトoに等しい)です。  
  16. ハッシュコード();  
  17. E get( int  索引);  
  18. Eセット int  インデックス、E要素);  
  19. void追加( int  インデックス、E要素);  
  20. E 削除( int  索引);  
  21. intインデックス(オブジェクト o);  
  22. int lastIndexOf(オブジェクト o);  
  23. リストイテレータ<E> リストイテレータ();  
  24. }

List ATD の一般的な実装には、ArrayList と LinkedList の 2 つがあります。

ArrayList の利点は、get および set 呼び出しに一定の時間がかかることです。欠点は、ArrayList の末尾が変更されない限り、新しい項目の挿入と既存の項目の削除にコストがかかることです。

LinkedList の利点は、テーブルの先頭での追加と削除に一定の時間がかかることです。欠点は、インデックス付けが容易ではなく、テーブルの末尾に近くな​​い限り、get 呼び出しにコストがかかることです。

  1. 公共 静的void makeList1(リスト<整数> lst, int n){
  2. クリア();
  3. ( int i = 0; i < n; i++)の場合{
  4. lst.add (i);
  5. }
  6. }

引数として ArrayList または LinkedList が渡されるかどうかに関係なく、add の各呼び出しはリストの末尾で行われるため、一定の時間がかかるため、makeList1 の実行時間は O(N) になります (ArrayList の偶発的な拡張は無視します)。一方、リストの先頭にいくつかの項目を追加してリストを作成すると、次のようになります。

  1. 公共 静的void makeList2(リスト<整数> lst, int n){
  2. クリア();
  3. ( int i = 0; i < n; i++)の場合{
  4. lst.add (i);
  5. }
  6. }

LinkedList の場合、実行時間は O(N) ですが、ArrayList の場合、先頭への追加は O(N) 操作であるため、実行時間は O(n^2) です。

  1. 公共 静的 整数 合計(リスト<整数> lst) {
  2. 整数合計= 0;
  3. ( int i = 0; i < n; i++)の場合{
  4. 合計+=lst.get(i);
  5.  
  6. }
  7. 合計を返します
  8. }

ここで、ArrayList の実行時間は O(N) ですが、LinkedList の場合、get の呼び出しは O(N) 操作であるため、実行時間は O(n^2) になります。ただし、拡張 for ループを使用すると、反復子が 1 つの項目から次の項目に効率的に進むため、どのリストに対しても O(N) 時間で実行されます。

ArrayList と LinkedList はどちらも、コレクションの contains メソッドと remove メソッドの呼び出しに線形時間がかかることから、検索には非効率的です。

例: LinkedList クラスで remove メソッドを使用する例 1: 6、5、1、4、2 の 5 つの数字があり、メソッドを呼び出した後にすべての偶数を削除する必要があるとします。

アイデア:

  1. すべての奇数を含む新しいテーブルを作成し、元のテーブルをクリアして、奇数をコピーし直します。
  2. 元のテーブルを直接走査し、偶数が見つかったらそれを削除します。

ArrayList と LinkedList はどちらも削除には非効率的です。LinkedList では、位置 i に到達するのにコストがかかります。

  1. 公共 静的void removEventVer1(リスト<整数> lst) {
  2. 整数i = 0;
  3. i < lst.size () の場合{
  4. (lst.get(i) % 2 == 0) の場合 {
  5. iを削除します。
  6. }それ以外{
  7. 私は++;
  8. }
  9. }
  10. }

LinkedListの場合、上記のソリューションの実行時間はO(n^2)です。イテレータを使用するとより効率的になります。もちろん、イテレータを使用する場合、Listの

削除しないと、次の例のように例外がスローされます (拡張 for ループでは、下部でイテレータが引き続き使用されます)。

  1. 公共 静的void removEventVer2(リスト<整数> lst) {
  2. (整数x : lst ) {
  3. (x % 2 == 0)の場合{
  4. lst.remove(x);
  5. }
  6.  
  7. }
  8. }

上記の問題を解決するには、イテレータの remove メソッドを直接使用できます。これは合法です。

  1. 公共 静的void removEventVer3(リスト<整数> lst) {
  2. イテレータ<整数> itr = lst.iterator();
  3. itr.hasNext() が続く間{
  4. if ( itr.next ()%2==0){
  5. itr.remove();
  6. }
  7. }
  8.  
  9. }

Iterator を使用した後、Iterator は削除する必要のあるノードに既に配置されているため、LinkedList の削除操作には O(n) の時間がかかります。

Iterator を使用しても、ArrayList の削除メソッドは、配列要素を削除するときに移動する必要があるため、依然として O(n^2) です。

ListIterator インターフェース ListIterator インターフェースは Iterator、hasNext、hasPrevious メソッドを拡張し、先頭からも末尾からも走査できるようにします。add は現在の位置に新しい項目を挿入し、set メソッドは hasNext または hasPrevious を呼び出して Iterator によって返された現在の値を変更します。

  1. パブリックインターフェースListIterator<E>はIterator<E>を拡張します{
  2. ブール型hasNext();
  3. ブール型 hasPrevious();
  4. void 削除();
  5. 集合(E e);
  6. voidを追加します(E e);

ArrayList の実装

次に、ArrayList を独自に記述し、ジェネリックをサポートします。コードは次のとおりです。

  1. パブリッククラス MyArrayList<T> は Iterable<T> を実装します {
  2.  
  3. プライベート静的最終int DEFAULT_CAPACITY = 10;
  4. プライベートT[] mArray;
  5. プライベートint mArraySize;
  6.  
  7. @オーバーライド
  8. パブリックイテレータ<T>イテレータ() {
  9. 新しいArrayIterator()を返します
  10. }  
  11.  
  12. プライベートクラスArrayIteratorはIterator<T>を実装します。
  13. プライベートint現在の位置;  
  14. @オーバーライド
  15. パブリックブールhasNext() {
  16. 現在の位置 < mArraySizeを返します
  17. }
  18.  
  19. @オーバーライド
  20. パブリックT(){
  21. if (!hasNext()) {
  22. 新しい NoSuchElementException() をスローします。
  23. }
  24.  
  25. mArray[currentPositon++]を返します
  26. }
  27.  
  28. @オーバーライド
  29. パブリックボイド削除() {
  30. MyArrayList.this.remove( --currentPositon);  
  31. }
  32. }   
  33. パブリックボイドトリムサイズ() {
  34. 容量を確保する(サイズ());
  35. }
  36.  
  37. 公共 整数 サイズ() {
  38. mArraySizeを返します
  39. }
  40.  
  41. パブリックブール値isEmpty() {
  42. mArraySize == 0を返します
  43. }  
  44. パブリックMyArrayList( int  サイズ) {
  45. サイズDEFAULT_CAPACITY より小さい場合
  46. mArraySize =サイズ;
  47. }それ以外{
  48. 容量を確保します(DEFAULT_CAPACITY);
  49. }
  50. }
  51.  
  52. プライベートvoidのensureCapacity( int newCapacity) {
  53. T[] newArray = (T[]) 新しいオブジェクト[新しい容量];
  54. ( int i = 0; i < mArray.length; i++) {
  55. 新しい配列[i] = mArray[i];
  56. }
  57. mArray = 新しい配列;
  58. }  
  59. パブリックブール値add (T t) {
  60. (t, mArraySize)を追加します
  61. 戻る 真実;
  62. }
  63.  
  64. パブリックvoid add (T t, int位置) {
  65. mArraySize == mArray.length の場合 {
  66. 容量を確保します(mArraySize * 2 + 1);
  67. }
  68. ( int i = 位置; i < mArraySize - 1; i++) {
  69. mArray[i + 1] = mArray[i];
  70. }
  71. mArray[位置] = t;
  72. ++m配列サイズ;
  73. }
  74.  
  75. パブリックT reomve() {
  76. 戻り値:削除(mArraySize);
  77. }
  78.  
  79. プライベートT削除( int位置){
  80. T t = mArray[位置];
  81. ( int i = 位置; i < mArraySize - 1; i++) {
  82. mArray[i] = mArray[i + 1];
  83. }
  84. --m配列サイズ;  
  85. tを返します
  86. }
  87.  
  88. パブリックT get( int位置) {
  89. if (位置 < 0 || 位置 > mArraySize) {
  90. 新しいArrayIndexOutOfBoundsException()をスローします。
  91. }
  92. mArray[位置]を返します
  93. }
  94.  
  95. パブリックTセット(T t) {
  96. 戻る  (t, mArraySize - 1)を設定します
  97. }
  98.  
  99. パブリックTセット(T t、 int位置) {
  100. if (位置 < 0 || 位置 > mArraySize) {
  101. 新しいArrayIndexOutOfBoundsException()をスローします。
  102. }
  103. T old = mArray[位置];
  104. mArray[位置] = t;
  105. 古いものを返す;
  106. }
  107. }

ここで言及しておくべきことは、新しいT[]を直接作成することはできないが、次のコードを通じて汎用配列を作成する必要があるということである。

  1. T[] newArray = (T[]) 新しいオブジェクト[新しい容量];

もう一つ言及する価値のある点は、ArrayIteratorでMyArrayList.this.removeを使用するのは、イテレータ自身の削除との競合を避けるためである。

  1. @オーバーライド
  2. パブリックボイド削除() {
  3. MyArrayList.this.remove( --currentPositon);  
  4. }

LinkedList の実装

LinkedList では、最前面のノードはヘッド ノードと呼ばれ、最背面のノードはテール ノードと呼ばれます。これら 2 つの追加ノードの存在により、多くの特殊なケースが排除され、コーディングが大幅に簡素化されます。たとえば、ヘッド ノードが使用されていない場合、最初のノードを削除するのは特別なケースです。これは、削除中にリンク リストを最初のノードのチェーンに再調整する必要があり、削除アルゴリズムでは通常、削除されたノードの前のノードを参照する必要があるためです (ヘッド ノードがない場合、最初のノードの前にノードがない特別なケースになります)。

  1. パブリッククラス MyLinkedList<T> は Iterable<T> を実装します {
  2.  
  3. プライベート Node<T> headNote;
  4. プライベートNode<T> endNote;
  5.  
  6. プライベートint mSize;
  7. プライベートint modCount;
  8.  
  9. パブリックMyLinkedList() {
  10. 初期化();
  11. }
  12.  
  13. プライベートvoid init() {
  14. headNote = 新しい Node<>( null , null , null );
  15. endNote = 新しい Node<>( null 、 headNote, null );
  16. headNote.mNext = endNote;
  17.  
  18. mサイズ = 0;
  19. modCount++;
  20. }
  21.  
  22. 公共 整数 サイズ() {
  23. mSizeを返します
  24. }
  25.  
  26. パブリックブール値isEmpty() {
  27. mSize == 0を返します
  28. }
  29.  
  30. パブリックブール値add (T t) {
  31. 前に追加します(t、サイズ());
  32. 戻る 真実;
  33. }
  34.  
  35. パブリックT get( int  索引) {
  36. Node<T> temp = getNode(インデックス、 0、サイズ());
  37. 戻る 一時.mData;
  38. }
  39.  
  40. パブリックT削除( int位置){
  41. Node<T> tempNode = getNode(位置);
  42. 戻り値:削除(tempNode);
  43. }
  44.  
  45. プライベートT削除(Node<T> tempNode) {
  46. tempNode.mPre.mNext = tempNode.mNext;
  47. tempNode.mNext.mPre = tempNode.mPre;
  48. mサイズ--;  
  49. modCount++;
  50. tempNode.mDataを返します
  51. }
  52.  
  53. パブリックTセット int  インデックス、T t) {
  54. Node<T> tempNode = getNode(インデックス);
  55. T old = tempNode.mData;
  56. tempNode.mData = t;
  57. 古いものを返す;
  58. }
  59.  
  60. プライベートNode<T> getNode( int  索引) {
  61. getNode(インデックス、0、サイズ() - 1 )を返します
  62. }
  63.  
  64.  
  65.  
  66. プライベートNode<T> getNode( int  インデックス int  下位 int  ){
  67. ノード<T> tempNode;
  68.  
  69. if (下限< 0 ||上限> mSize) {
  70. 新しい IndexOutOfBoundsException() をスローします。
  71. }
  72.  
  73. if (インデックス< mSize / 2) {
  74. tempNode = headNote.mNext;
  75. ( int i = 0; i <インデックス; i++) {
  76. tempNode = tempNode.mNext;
  77. }
  78. }それ以外{
  79. tempNode = endNote;
  80. ( int i = mSize; i >インデックス; i --) {  
  81. tempNode = tempNode.mPre;
  82. }
  83. }
  84. tempNodeを返します
  85. }
  86.  
  87.  
  88. プライベート静的クラスNode<T> {
  89.  
  90. プライベートNode<T> mNext;
  91. プライベートTmData;
  92. プライベートNode<T> mPre;
  93.  
  94. パブリックNode(Tデータ、Node<T> pre、Node<T> next ) {
  95. mData = データ;
  96. mPre = 前;
  97. mNext =次へ;
  98. }
  99. }
  100.  
  101.  
  102. プライベートクラス LinkedListIterator は Iterator<T> を実装します {
  103. プライベート Node<T> currentNode = headNote.mNext;
  104. プライベートint expectedModCount = modCount;
  105. プライベートブール値okToMove;
  106.  
  107. @オーバーライド
  108. パブリックブールhasNext() {
  109. currentNode != endNoteを返します
  110. }
  111.  
  112. @オーバーライド
  113. パブリックT(){
  114. (modCount != 期待されるModCount)の場合{
  115. 新しいConcurrentModificationException()をスローします。
  116. }
  117. if (!hasNext()) {
  118. 新しい NoSuchElementException() をスローします。
  119. }
  120. T t = currentNode.mData;
  121. 現在のノード = 現​​在のノード.mNext;
  122. okToMove = true ;
  123. tを返します
  124. }
  125.  
  126. @オーバーライド
  127. パブリックボイド削除() {
  128. (modCount != 期待されるModCount)の場合{
  129. 新しいConcurrentModificationException()をスローします。
  130. }
  131. 移動が成功すれば
  132. 新しい IllegalStateException() をスローします。
  133. }
  134. MyLinkedList.this.remove(currentNode.mPre);
  135. 予想されるModCount++;
  136. okToMove = false ;
  137. }
  138.  
  139. @オーバーライド
  140. パブリックイテレータ<T>イテレータ() {
  141. 新しい LinkedListIterator()を返します
  142. }
  143. }

1. modCount は、リンク リストの作成以降に行われた変更の数を表します。追加または削除の各呼び出しにより、modCount が更新されます。アイデアとしては、イテレータが作成されると、コレクションの modCount が格納されます。イテレータ メソッド (next または remove) の各呼び出しでは、リンク リスト内の現在の modCount をイテレータに格納されている modCount と比較し、2 つのカウントが一致しない場合は ConcurrentModificationException をスローします。

2. LinkedListIterator では、currentNode は next の呼び出しによって返される項目を含むノードを表します。 currentNode が endNote に配置されている場合、next の呼び出しは無効であることに注意してください。

LinkedListIterator の削除メソッドでは、ArrayIterator とは異なり、currentNode ノードは前のノードの削除の影響を受けないため、currentNode は変更されません (ArrayIterator では、項目が移動され、current を更新する必要があります)。

<<:  CNN モデルの圧縮と加速アルゴリズムのレビュー

>>:  AIへの幻滅? AIの発展を妨げる8つのトレンド

ブログ    
ブログ    
ブログ    

推薦する

米軍はドローンに対処するための新たな方法を考案した。ドローンの群れを破壊するマイクロ波兵器を開発するのだ。

【環球時報記者 徐陸明】6月17日、「国防ニュース」ウェブサイトの報道によると、最新の軍事予算文書...

Facebookが開発した高速データ圧縮アルゴリズムZstdの使い方

[51CTO.com クイック翻訳] Zstandard (Zstd とも呼ばれる) は、Faceb...

Gizwits Cloud はスマートホームが機械にユーザーをよりよく理解するのを助けます

[51CTO.com からのオリジナル記事] 2016年、国内投資家のVRへの熱意はまだ薄れていなか...

コカ・コーラの新たな試み:アートや広告制作における生成AIの活用

生成型 AI の新たな波に直面して、私たちはそれに積極的に適応するか、AI (または AI を受け入...

面接でコンシステントハッシュアルゴリズムについて再度質問されました。この答えは面接官を即死させるでしょう!

[[284994]]データシャーディングまずは例を見てみましょう。多くの場合、キャッシュには Re...

...

プロのアニメーターがGANを使って「怠け者」を助ければ、数週間かかる仕事を数分で終わらせられる

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

人工知能が都市景観をどう変えるのか

人工知能 (AI) とディープラーニングはあらゆるところに存在し、今や都市の景観を一変させる可能性を...

古代都市ポンペイを「ハイテク」な方法で訪れるにはどうすればいいでしょうか?

ビッグデータダイジェスト制作著者: カレブ西暦79年、ベスビオ山が噴火し、その麓にあったポンペイの街...

6種類の負荷分散アルゴリズムの概要

C言語を学んだ友人やIT関係の人ならアルゴリズムには詳しいと思います。したがって、分野が異なれば、ア...

2024年の産業用ロボットの開発動向

産業用ロボットは、さまざまな産業用タスクを自動的に実行できる一種の機器として、製造、組み立て、梱包、...

グリーンロボットが環境の持続可能性にどのように貢献できるか

グリーンロボットは気候変動と闘い、より良い未来へと導くのに役立ちます。私たちは通常、ロボットが「環境...

スループットが約30倍に増加しました。田元東チームの最新論文は、大規模モデル展開の問題を解決している

大規模言語モデル (LLM) は今年非常に人気がありました。しかし、その驚異的な効果の背後には、巨大...

2020年の人工知能レビュー:AIが時代に知性をもたらす

2020年は人工知能(AI)にとって節目の年です。今年、新型コロナウイルス感染症のパンデミックが世界...