线性表及其逻辑结构 线性表是最简单也是最常用的一种数据结构。 英文字母表(A、B、…、Z)是一个线性表,表中每个英文字母是一个数据元素;成绩单是一个线性表,表中每一行是一个数据元素,每个数据元素又由学号、姓名、成绩等数据项组成。
线性表的定义 线性表是具有相同特性的数据元素的一个有限序列。线性表一般表示为:
1 L = (a1, a2, …, ai,ai+1 ,…, an)
线性表中元素在位置上是有序的,这种位置上有序性就是一种线性关系,用二元组表示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 L = (D, R) D = {ai| 1 ≤i≤n, n≥0 } R = {r} r = {<ai, ai+1 > | 1 ≤i≤n-1 } ``` ### 线性表的抽象数据类型描述 将线性表数据结构抽象成为一种数据类型,这个数据类型中包含数据元素、元素之间的关系、操作元素的基本算法。 对于基本数据类型(int 、float 、boolean 等等)java已经帮我们实现了用于操作他们的基本运算, 我们需要基于这些基本运算,为我们封装的自定义数据类型提供操作它们的算法。 比如数组就是一种被抽象出来的线性表数据类型,数组自带很多基本方法用于操作数据元素。 Java中的List我们经常会使用到,但是很少关注其内部实现,List是一个接口, 里面定义了一些抽象的方法,其目的就是对线性表的抽象,其中的方法就是线性表的一些常用基本运算。 而对于线性表的不同**存储结构**其实现方式就有所不同了, 比如**ArrayList**是对线性表顺序存储结构的实现, **LinkedList**是线性表链式存储结构的实现等。 存储结构没有确定我们就不知道数据怎么存储,但是对于线性表这种逻辑结构中数据的基本操作我们可以预知, 无非就是获取长度、获取指定位置的数据、插入数据、删除数据等等操作,可以参考List。 对于本系列文章,只是对数据结构和一些常用算法学习,接下来的代码将选择性实现并分析算法。 对线性表的抽象数据类型描述如下: ```java public interface IList <T > { boolean isEmpty () ; int length () ; boolean add (int index, T data) ; boolean add (T data) ; T remove (int index) ; boolean remove (T data) ; boolean removeAll (T data) ; void clear () ; T set (int index, T data) ; boolean contains (T data) ; int indexOf (T data) ; int lastIndexOf (T data) ; T get (int index) ; String toString () ; }
线性表的顺序存储结构
顺序表 把线性表中的所有元素按照其逻辑顺序依次存储在计算机存储器中指定存储位置开始的一块连续的存储空间中。在Java中创建一个数组对象就是分配了一块可供用户使用的连续的存储空间,该存储空间的起始位置就是由数组名表示的地址常量。线性表的顺序存储结构是利用数组来实现的。 在Java中,我们通常利用下面的方式来使用数组:
1 2 3 int [] array = new int []{1 ,2 ,3 }; Array.getInt(array, 0 ); Array.set(array, 0 , 1 );
Array这种方式创建的数组是固定长度的,其容量无法修改 ,当array被创建出来的时候,系统只为其分配3个存储空间,所以我们无法对其进行添加和删除操作。Array这个类里面提供了很多方法用于操作数组,这些方法都是静态的,所以Array是一个用于操作数组的工具类,这个类提供的方法只有两种:get和set,所以只能获取和设置数组中的元素,然后对于这两种操作,我们通常使用array[i]、array[i] = 0的简化方式,所以Array这个类用的比较少。 另外一种数组ArrayList,其内部维护了一个数组,所以本质上也是数组,其操作都是对数组的操作,与上述数组不同的是,ArrayList是一种可变长度的数组。 既然数组创建时就已经分配了存储空间,为什么ArrayList是长度可变的呢?长度可变意味着可以从数组中添加、删除元素,向ArrayList中添加数据时,实际上是创建了一个新的数组,将原数组中元素一个个复制到新数组后,将新元素添加进来。 如果ArrayList仅仅做了这么简单的操作,那他就不应该出现了。ArrayList中的数组长度是大于等于其元素个数的,当执行add()操作时首先会检查数组长度是否够用,只有当数组长度不够用时才会创建新的数组,由于创建新数组意味着老数据的搬迁,所以这个机制也算是利用空间换取时间上的效率。但是如果添加操作并不是尾部添加,而是头部或者中间位置插入,也避免不了元素位置移动。
顺序表基本运算的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 public class LinearArray <T > implements IList <T > { private Object[] datas; public static <T> LinearArray<T> createArray (T[] objs) { LinearArray<T> array = new LinearArray(); array.datas = new Object[objs.length]; for (int i = 0 ; i<objs.length; i++) array.datas[i] = objs[i]; return array; } private LinearArray () { } @Override public boolean isEmpty () { return datas.length == 0 ; } @Override public int length () { return datas.length; } @Override public T get (int index) { if (index<0 || index >= datas.length) throw new IndexOutOfBoundsException(); return (T) datas[index]; } @Override public T set (int index, T data) { if (index<0 || index >= datas.length) throw new IndexOutOfBoundsException(); T oldValue = (T) datas[index]; datas[index] = data; return oldValue; } @Override public boolean contains (T data) { return indexOf(data) >= 0 ; } @Override public int indexOf (T data) { if (data == null ) { for (int i = 0 ; i < datas.length; i++) if (datas[i]==null ) return i; } else { for (int i = 0 ; i < datas.length; i++) if (data.equals(datas[i])) return i; } return -1 ; } @Override public int lastIndexOf (T data) { if (data == null ) { for (int i = datas.length-1 ; i >= 0 ; i--) if (datas[i]==null ) return i; } else { for (int i = datas.length-1 ; i >= 0 ; i--) if (data.equals(datas[i])) return i; } return -1 ; } @Override public boolean add (int index, T data) { if (index > datas.length || index < 0 ) throw new IndexOutOfBoundsException(); Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); destination[index] = data; System.arraycopy(datas, index, destination, index + 1 , datas.length - index); datas = destination; return true ; } @Override public boolean add (T data) { Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , datas.length); destination[datas.length] = data; datas = destination; return true ; } public boolean addByOrder (int data) { int index = 0 ; while (index<datas.length && (int )datas[index]<data) index++; if ((int )datas[index] == data) return false ; Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); System.arraycopy(datas, index, destination, index+1 , datas.length-index); destination[index] = data; datas = destination; return true ; } @Override public T remove (int index) { if (index >= datas.length || index < 0 ) throw new IndexOutOfBoundsException(); T oldValue = (T) datas[index]; fastRemove(index); return oldValue; } @Override public boolean remove (T data) { if (data == null ) { for (int index = 0 ; index < datas.length; index++) if (datas[index] == null ) { fastRemove(index); return true ; } } else { for (int index = 0 ; index < datas.length; index++) if (data.equals(datas[index])) { fastRemove(index); return true ; } } return false ; } private void fastRemove (int index) { Object destination[] = new Object[datas.length - 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); System.arraycopy(datas, index+1 , destination, index, datas.length - index-1 ); datas = destination; } @Override public boolean removeAll (T data) { return false ; } @Override public void clear () { datas = new Object[]{}; } @Override public String toString () { if (isEmpty()) return "" ; String str = "[" ; for (int i = 0 ; i<datas.length; i++){ str += (datas[i]+", " ); } str = str.substring(0 , str.lastIndexOf(", " )); return str+"]" ; } }
算法分析: 插入元素:删除元素: **
线性表的链式存储结构 顺序表必须占用一整块事先分配大小固定的存储空间,这样不便于存储空间的管理。为此提出了可以实现存储空间动态管理的链式存储方式–链表。
链表 在链式存储中,每个存储结点不仅包含元素本身的信息(数据域),还包含元素之间逻辑关系的信息,即一个结点中包含有直接后继结点的地址信息,这称为指针域。这样可以通过一个结点的指针域方便的找到后继结点的位置。 由于顺序表中每个元素至多只有一个直接前驱元素和一个直接后继元素。当采用链式存储时,一种最简单也最常用的方法是: 在每个结点中除包含数据域外,只设置一个指针域用以指向其直接后继结点,这种构成的链接表称为线性单向链接表,简称单链表。
另一种方法是,在每个结点中除包含数值域外,设置两个指针域,分别用以指向直接前驱结点和直接后继结点,这样构成的链接表称为线性双向链接表,简称双链表。
单链表当访问一个结点后,只能接着访问它的直接后继结点,而无法访问他的直接前驱结点。双链表则既可以依次向后访问每个结点,也可以依次向前访问每个结点。
单链表结点元素类型定义:
1 2 3 4 public class LNode { protected LNode next; protected Object data; }
双链表结点元素类型定义:
1 2 3 4 5 public class DNode { protected DNode prior; protected DNode next; protected Object data; }
在顺序表中,逻辑上相邻的元素,对应的存储位置也相邻,所以进行插入或删除操作时,通常需要平均移动半个表的元素,这是相当费时的操作。 在链表中,每个结点存储位置可以任意安排,不必要求相邻,插入或删除操作只需要修改相关结点的指针域即可,方便省时。对于单链表,如果要在结点p之前插入一个新结点,由于通过p并不能找到其前驱结点,我们需要从链表表头遍历至p的前驱结点然后进行插入操作,这样时间复杂度就是O(n),而顺序表插入删除结点时间复杂度也是O(n),那为什么说链表插入删除操作更加高效呢?因为单链表插入删除操作所消耗的时间主要在于查找前驱结点,这个查找工作的时间复杂度为O(n),而真正超如删除时间为O(1) 还有顺序表需要移动结点,移动结点通常比单纯的查找更加费时,链表不需要连续的空间,不需要扩容创建新表,所以同样时间复杂度O(n),链表更适合插入和删除操作。对于遍历查找前驱结点的问题,在双链表中就能很好的解决, 双链表在已知某结点的插入和删除操作时间复杂度是O(1)。 由于链表的每个结点带有指针域,从存储密度来讲,这是不经济的。所谓存储密度是指结点数据本身所占存储量和整改结点结构所占存储量之比。 3.2 单链表基本运算的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 public class LinkList <T > implements IList <T > { public LNode<T> head; public static <T> LinkList<T> createListF (T[] array) { LinkList llist = new LinkList(); if (array!=null && array.length>0 ) { for (T obj : array) { LNode<T> node = new LNode(); node.data = obj; node.next = llist.head; llist.head = node; } } return llist; } public static <T> LinkList<T> createListR (T[] array) { LinkList llist = new LinkList(); if (array!=null && array.length>0 ){ llist.head = new LNode(); llist.head.data = array[0 ]; LNode<T> temp = llist.head; for (int i = 1 ; i < array.length; i++){ LNode node = new LNode(); node.data = array[i]; temp.next = node; temp = node; } } return llist; } @Override public boolean isEmpty () { return head==null ; } @Override public int length () { if (head==null ) return 0 ; int l = 1 ; LNode node = head; while (node.next!=null ) { l++; node = node.next; } return l; } @Override public void clear () { head = null ; } @Override public T set (int index, T data) { return null ; } @Override public boolean contains (T data) { return false ; } @Override public T get (int index) { return getNode(index).data; } public LNode<T> getNode (int index) { LNode node = head; int j = 0 ; while (j < index && node!=null ){ j++; node = node.next; } return node; } @Override public int indexOf (T data) { if (head==null ) return -1 ; LNode node = head; int j = 0 ; while (node!=null ){ if (node.data.equals(data)) return j; j++; node = node.next; } return -1 ; } @Override public int lastIndexOf (T data) { if (head==null ) return -1 ; int index = -1 ; LNode node = head; int j = 0 ; while (node!=null ){ if (node.data.equals(data)) { index = j; } j++; node = node.next; } return index; } public LNode<T> getReNode (int k) { if (head==null ) return null ; int len = length(); if (k > len) return null ; LNode target = head; LNode next = head; for (int i=0 ;i < k;i++) next = next.next; while (next!=null ){ target = target.next; next = next.next; } return target; } public LNode getMiddleNode () { if (head == null || head.next == null ) return head; LNode target = head; LNode temp = head; while (temp != null && temp.next != null ){ target = target.next; temp = temp.next.next; } return target; } public static LNode mergeList (LNode head1, LNode head2) { if (head1==null ) return head2; if (head2==null ) return head1; LNode loop = head1; while (loop.next!=null ) loop = loop.next; loop.next = head2; return head1; } public static LNode mergeSortedListRec (LNode head1, LNode head2) { if (head1==null )return head2; if (head2==null )return head1; if (((int )head1.data)>((int )head2.data)) { head2.next = mergeSortedListRec(head2.next, head1); return head2; } else { head1.next = mergeSortedListRec(head1.next, head2); return head1; } } public void reverseListByLoop () { if (head == null || head.next == null ) return ; LNode pre = null ; LNode nex = null ; while (head != null ) { nex = head.next; head.next = pre; pre = head; head = nex; } head = pre; } public LNode reverseListByRec (LNode head) { if (head==null ||head.next==null ) return head; LNode reHead = reverseListByRec(head.next); head.next.next = head; head.next = null ; return reHead; } @Override public String toString () { if (head == null ) return "" ; LNode node = head; StringBuffer buffer = new StringBuffer(); while (node != null ){ buffer.append(node.data+" -> " ); node = node.next; } return buffer.toString(); } public static String display (LNode head) { if (head == null ) return "" ; LNode node = head; StringBuffer buffer = new StringBuffer(); while (node != null ){ buffer.append(" -> " +node.data); node = node.next; } return buffer.toString(); } public String displayReverseStack () { if (head == null ) return "" ; Stack <LNode> stack = new Stack < >(); LNode head = this .head; while (head!=null ){ stack.push(head); head=head.next; } StringBuffer buffer = new StringBuffer(); while (!stack.isEmpty()){ buffer.append(" -> " +stack.pop().data); } return buffer.toString(); } public void displayReverseRec (StringBuffer buffer, LNode head) { if (head==null ) return ; displayReverseRec(buffer, head.next); buffer.append(" -> " ); buffer.append(head.data); } @Override public boolean add (int index, T data) { if (index==0 ){ LNode temp = new LNode(); temp.next = head; return true ; } int j = 0 ; LNode node = head; while (j < index-1 && node!=null ){ j++; node = node.next; } if (node==null ) return false ; LNode temp = new LNode(); temp.data = data; temp.next = node.next; node.next = temp; return true ; } @Override public boolean add (T data) { LNode node = head; while (node!=null && node.next!=null ) node = node.next; LNode temp = new LNode(); temp.data = data; node.next = temp; return false ; } @Override public T remove (int index) { LNode<T> node = deleteNode(index); return node==null ?null :node.data; } public LNode deleteNode (int index) { LNode node = head; if (index==0 ){ if (node==null ) return null ; head = node.next; return node; } int j = 0 ; while (j < index-1 && node!=null ){ j++; node = node.next; } if (node==null ) return null ; LNode delete = node.next; if (delete==null ) return null ; node.next = delete.next; return delete; } @Override public boolean remove (T data) { return false ; } @Override public boolean removeAll (T data) { return false ; } public static void deleteNode (LNode head, LNode delete) { if (delete==null ) return ; if (delete.next==null ){ if (head==delete) head = null ; else { LNode temp = head; while (temp.next!=delete) temp = temp.next; temp.next=null ; } } else { delete.data = delete.next.data; delete.next = delete.next.next; } return ; } public static boolean hasCycle (LNode head) { LNode p1 = head; LNode p2 = head; while (p1!=null && p2!=null ){ p1 = p1.next; p2 = p2.next.next; if (p2 == p1) return true ; } return false ; } public static LNode getFirstNodeInCycleHashMap (LNode head) { LNode target = null ; HashMap<LNode,Boolean > map=new HashMap< >(); while (head != null ){ if (map.containsKey(head)) { target = head; break ; } else { map.put(head, true ); head = head.next; } } return target; } public static LNode getFirstNodeInCycle (LNode head) { LNode fast = head; LNode slow = head; while (fast != null && fast.next != null ){ slow = slow.next; fast = fast.next.next; if (slow == fast) break ; } if (fast == null ||fast.next == null ) return null ; slow=head; while (slow!=fast){ slow=slow.next; fast=fast.next; } return slow; } public static LNode isIntersect (LinkList list1, LinkList list2) { LNode head1 = list1.head; LNode head2 = list2.head; if (head1==null || head2==null )return null ; int len1 = list1.length(); int len2 = list2.length(); if (len1 >= len2){ for (int i=0 ;i < len1-len2;i++){ head1=head1.next; } }else { for (int i=0 ;i < len2-len1;i++){ head2=head2.next; } } while (head1 != null &&head2 != null ){ if (head1 == head2) return head1; head1=head1.next; head2=head2.next; } return null ; } }
算法分析
判断一个单链表中是否有环 我们可以通过HashMap判断,遍历结点,将结点值放入HashMap,如果某一刻发现当前结点在map中已经存在,则存在环,并且此结点正是环的入口,此算法见8.2方法。但是有一种问法是不通过任何其他数据结构怎么判断单链表是否存在环。 这样我们可利用的就只有单链表本身,一种解法是通过快慢指针,遍历链表,一个指针跳一步(慢指针步长为1),另一个指针跳两步(快指针步长为2),如果存在环,这两个指针必将在某一刻指向同一结点,假设此时慢指针跳了n步,则快指针跳的步数为n/2步:
判断两个单链表是否相交 由于单链表的特性(只有一个指针域),如果两个表相交,那必定是Y形相交,不会是X形相交,如图所示。两个单链表后面的结点相同,不同的部分只有前面,砍掉较长的链表的前面部分,然后两个链表同步遍历,必将指向同一个结点,这个结点就是交点:
双链表 双链表中每个结点有两个指针域,一个指向其直接后继结点,一个指向其直接前驱结点。 建立双链表也有两种方法,头插法和尾插法,这与创建单链表过程相似。在双链表中,有些算法如求长度、取元素值、查找元素等算法与单链表中相应算法是相同的。但是在单链表中,进行结点插入和删除时涉及前后结点的一个指针域的变化,而双链表中结点的插入和删除操作涉及前后结点的两个指针域的变化。java中LinkedList正是对双链表的实现,算法可参考此类。 双链表基本运算的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 public class DLinkList <T > implements IList <T > { transient DNode<T> first; transient DNode<T> last; private int size; public static <T> DLinkList<T> createListF (T[] array) { DLinkList dlist = new DLinkList(); if (array!=null && array.length>0 ) { dlist.size = array.length; for (T obj : array) { DNode<T> node = new DNode(); node.data = obj; node.next = dlist.first; if (dlist.first!=null ) dlist.first.prior = node; else dlist.last = node; dlist.first = node; } } return dlist; } public static <T> DLinkList<T> createListR (T[] array) { DLinkList dlist = new DLinkList(); if (array!=null && array.length>0 ){ dlist.size = array.length; dlist.first = new DNode<T>(); dlist.first.data = array[0 ]; dlist.last = dlist.first; for (int i = 1 ; i < array.length; i++){ DNode<T> node = new DNode(); node.data = array[i]; dlist.last.next = node; node.prior = dlist.last; dlist.last = node; } } return dlist; } @Override public boolean isEmpty () { return size==0 ; } @Override public int length () { return size; } @Override public boolean add (int index, T data) { if (index < 0 || index > size) throw new IndexOutOfBoundsException(); DNode<T> newNode = new DNode(); newNode.data = data; if (index == size) { final DNode<T> l = last; if (l == null ) first = newNode; else { l.next = newNode; newNode.prior = l; } last = newNode; size++; } else { DNode<T> indexNode = getNode(index); DNode<T> pred = indexNode.prior; newNode.prior = pred; newNode.next = indexNode; indexNode.prior = newNode; if (pred == null ) first = newNode; else pred.next = newNode; size++; } return false ; } @Override public boolean add (T data) { return add(size, data); } @Override public T remove (int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); return unlink(getNode(index)); } @Override public boolean remove (T data) { if (data == null ) { for (DNode<T> x = first; x != null ; x = x.next) { if (x.data == null ) { unlink(x); return true ; } } } else { for (DNode<T> x = first; x != null ; x = x.next) { if (data.equals(x.data)) { unlink(x); return true ; } } } return false ; } @Override public boolean removeAll (T data) { boolean result = false ; if (data == null ) { for (DNode<T> x = first; x != null ; x = x.next) { if (x.data == null ) { unlink(x); result = true ; } } } else { for (DNode<T> x = first; x != null ; x = x.next) { if (data.equals(x.data)) { unlink(x); result = true ; } } } return result; } private T unlink (DNode<T> x) { final T element = x.data; final DNode<T> next = x.next; final DNode<T> prev = x.prior; if (prev == null ) { first = next; } else { prev.next = next; x.prior = null ; } if (next == null ) { last = prev; } else { next.prior = prev; x.next = null ; } x.data = null ; size--; return element; } @Override public void clear () { for (DNode<T> x = first; x != null ; ) { DNode<T> next = x.next; x.data = null ; x.next = null ; x.prior = null ; x = next; } first = last = null ; size = 0 ; } @Override public T set (int index, T data) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); DNode<T> x = getNode(index); T oldVal = x.data; x.data = data; return oldVal; } @Override public boolean contains (T data) { return indexOf(data) != -1 ; } @Override public int indexOf (T data) { int index = 0 ; if (data == null ) { for (DNode<T> x = first; x != null ; x = x.next) { if (x.data == null ) return index; index++; } } else { for (DNode<T> x = first; x != null ; x = x.next) { if (data.equals(x.data)) return index; index++; } } return -1 ; } @Override public int lastIndexOf (T data) { int index = size; if (data == null ) { for (DNode<T> x = last; x != null ; x = x.prior) { index--; if (x.data == null ) return index; } } else { for (DNode<T> x = last; x != null ; x = x.prior) { index--; if (data.equals(x.data)) return index; } } return -1 ; } @Override public T get (int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); return getNode(index).data; } private DNode<T> getNode (int index) { if (index < (size >> 1 )) { DNode<T> x = first; for (int i = 0 ; i < index; i++) x = x.next; return x; } else { DNode<T> x = last; for (int i = size - 1 ; i > index; i--) x = x.prior; return x; } } public void reverse () { last = first; DNode now = first; DNode next; while (now!=null ){ next = now.next; now.next = now.prior; now.prior = next; first = now; now = next; } } @Override public String toString () { if (size == 0 ) return "" ; DNode node = first; StringBuffer buffer = new StringBuffer(); buffer.append(" " ); while (node != null ){ buffer.append(node.data+" -> " ); node = node.next; } buffer.append("next\npre" ); node = last; int start = buffer.length(); LogUtil.i(getClass().getSimpleName(), "buffer长度:" +buffer.length()); while (node != null ){ buffer.insert(start ," <- " +node.data); node = node.prior; } return buffer.toString(); } }
算法分析 双链表与单链表不同之处在于,双链表能从两端依次访问各个结点。单链表相对于顺序表优点是插入、删除数据更方便,但是访问结点需要遍历,时间复杂度为O(n); 双链表就是在单链表基础上做了一个优化,使得访问结点更加便捷(从两端),这样从近的一端出发时间复杂度变为O(n/2),虽然不是指数阶的区别,但也算是优化。双链表在插入、删除结点时逻辑比单链表稍麻烦:
循环链表 循环链表是另一种形式的链式存储结构,它的特点是表中尾结点的指针域不再是空,而是指向头结点,整个链表形成一个环。由此,从表中任意一结点出发均可找到链表中其他结点。如图所示为带头结点的循环单链表和循环双链表:
有序表 所谓有序表,是指所有结点元素值以递增或递减方式排列的线性表,并规定有序表中不存在元素值相同的结点。 有序表可以采用顺序表和链表进行存储,若以顺序表存储有序表,其算法除了add(T data)以外,其他均与前面说的顺序表对应的运算相同。有序表的add(T data)操作不是插入到末尾,而需要遍历比较大小后插入相应位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public boolean addByOrder (int data) { int index = 0 ; while (index<datas.length && (int )datas[index]<data) index++; if ((int )datas[index] == data) return false ; Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); System.arraycopy(datas, index, destination, index+1 , datas.length-index); destination[index] = data; datas = destination; return true ; }
最后 想强调的是 由于顺序表结构的底层实现借助的就是数组,因此对于初学者来说,可以把顺序表完全等价为数组,但实则不是这样。数据结构是研究数据存储方式的一门学科,它囊括的都是各种存储结构,而数组只是各种编程语言中的基本数据类型,并不属于数据结构的范畴。
数据结构 - 线性表
线性表及其逻辑结构 线性表是最简单也是最常用的一种数据结构。 英文字母表(A、B、…、Z)是一个线性表,表中每个英文字母是一个数据元素;成绩单是一个线性表,表中每一行是一个数据元素,每个数据元素又由学号、姓名、成绩等数据项组成。
线性表的定义 线性表是具有相同特性的数据元素的一个有限序列。线性表一般表示为:
1 L = (a1, a2, …, ai,ai+1 ,…, an)
线性表中元素在位置上是有序的,这种位置上有序性就是一种线性关系,用二元组表示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 L = (D, R) D = {ai| 1 ≤i≤n, n≥0 } R = {r} r = {<ai, ai+1 > | 1 ≤i≤n-1 } ``` ### 线性表的抽象数据类型描述 将线性表数据结构抽象成为一种数据类型,这个数据类型中包含数据元素、元素之间的关系、操作元素的基本算法。 对于基本数据类型(int 、float 、boolean 等等)java已经帮我们实现了用于操作他们的基本运算, 我们需要基于这些基本运算,为我们封装的自定义数据类型提供操作它们的算法。 比如数组就是一种被抽象出来的线性表数据类型,数组自带很多基本方法用于操作数据元素。 Java中的List我们经常会使用到,但是很少关注其内部实现,List是一个接口, 里面定义了一些抽象的方法,其目的就是对线性表的抽象,其中的方法就是线性表的一些常用基本运算。 而对于线性表的不同**存储结构**其实现方式就有所不同了, 比如**ArrayList**是对线性表顺序存储结构的实现, **LinkedList**是线性表链式存储结构的实现等。 存储结构没有确定我们就不知道数据怎么存储,但是对于线性表这种逻辑结构中数据的基本操作我们可以预知, 无非就是获取长度、获取指定位置的数据、插入数据、删除数据等等操作,可以参考List。 对于本系列文章,只是对数据结构和一些常用算法学习,接下来的代码将选择性实现并分析算法。 对线性表的抽象数据类型描述如下: ```java public interface IList <T > { boolean isEmpty () ; int length () ; boolean add (int index, T data) ; boolean add (T data) ; T remove (int index) ; boolean remove (T data) ; boolean removeAll (T data) ; void clear () ; T set (int index, T data) ; boolean contains (T data) ; int indexOf (T data) ; int lastIndexOf (T data) ; T get (int index) ; String toString () ; }
线性表的顺序存储结构
顺序表 把线性表中的所有元素按照其逻辑顺序依次存储在计算机存储器中指定存储位置开始的一块连续的存储空间中。在Java中创建一个数组对象就是分配了一块可供用户使用的连续的存储空间,该存储空间的起始位置就是由数组名表示的地址常量。线性表的顺序存储结构是利用数组来实现的。 在Java中,我们通常利用下面的方式来使用数组:
1 2 3 int [] array = new int []{1 ,2 ,3 }; Array.getInt(array, 0 ); Array.set(array, 0 , 1 );
Array这种方式创建的数组是固定长度的,其容量无法修改 ,当array被创建出来的时候,系统只为其分配3个存储空间,所以我们无法对其进行添加和删除操作。Array这个类里面提供了很多方法用于操作数组,这些方法都是静态的,所以Array是一个用于操作数组的工具类,这个类提供的方法只有两种:get和set,所以只能获取和设置数组中的元素,然后对于这两种操作,我们通常使用array[i]、array[i] = 0的简化方式,所以Array这个类用的比较少。 另外一种数组ArrayList,其内部维护了一个数组,所以本质上也是数组,其操作都是对数组的操作,与上述数组不同的是,ArrayList是一种可变长度的数组。 既然数组创建时就已经分配了存储空间,为什么ArrayList是长度可变的呢?长度可变意味着可以从数组中添加、删除元素,向ArrayList中添加数据时,实际上是创建了一个新的数组,将原数组中元素一个个复制到新数组后,将新元素添加进来。 如果ArrayList仅仅做了这么简单的操作,那他就不应该出现了。ArrayList中的数组长度是大于等于其元素个数的,当执行add()操作时首先会检查数组长度是否够用,只有当数组长度不够用时才会创建新的数组,由于创建新数组意味着老数据的搬迁,所以这个机制也算是利用空间换取时间上的效率。但是如果添加操作并不是尾部添加,而是头部或者中间位置插入,也避免不了元素位置移动。
顺序表基本运算的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 public class LinearArray <T > implements IList <T > { private Object[] datas; public static <T> LinearArray<T> createArray (T[] objs) { LinearArray<T> array = new LinearArray(); array.datas = new Object[objs.length]; for (int i = 0 ; i<objs.length; i++) array.datas[i] = objs[i]; return array; } private LinearArray () { } @Override public boolean isEmpty () { return datas.length == 0 ; } @Override public int length () { return datas.length; } @Override public T get (int index) { if (index<0 || index >= datas.length) throw new IndexOutOfBoundsException(); return (T) datas[index]; } @Override public T set (int index, T data) { if (index<0 || index >= datas.length) throw new IndexOutOfBoundsException(); T oldValue = (T) datas[index]; datas[index] = data; return oldValue; } @Override public boolean contains (T data) { return indexOf(data) >= 0 ; } @Override public int indexOf (T data) { if (data == null ) { for (int i = 0 ; i < datas.length; i++) if (datas[i]==null ) return i; } else { for (int i = 0 ; i < datas.length; i++) if (data.equals(datas[i])) return i; } return -1 ; } @Override public int lastIndexOf (T data) { if (data == null ) { for (int i = datas.length-1 ; i >= 0 ; i--) if (datas[i]==null ) return i; } else { for (int i = datas.length-1 ; i >= 0 ; i--) if (data.equals(datas[i])) return i; } return -1 ; } @Override public boolean add (int index, T data) { if (index > datas.length || index < 0 ) throw new IndexOutOfBoundsException(); Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); destination[index] = data; System.arraycopy(datas, index, destination, index + 1 , datas.length - index); datas = destination; return true ; } @Override public boolean add (T data) { Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , datas.length); destination[datas.length] = data; datas = destination; return true ; } public boolean addByOrder (int data) { int index = 0 ; while (index<datas.length && (int )datas[index]<data) index++; if ((int )datas[index] == data) return false ; Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); System.arraycopy(datas, index, destination, index+1 , datas.length-index); destination[index] = data; datas = destination; return true ; } @Override public T remove (int index) { if (index >= datas.length || index < 0 ) throw new IndexOutOfBoundsException(); T oldValue = (T) datas[index]; fastRemove(index); return oldValue; } @Override public boolean remove (T data) { if (data == null ) { for (int index = 0 ; index < datas.length; index++) if (datas[index] == null ) { fastRemove(index); return true ; } } else { for (int index = 0 ; index < datas.length; index++) if (data.equals(datas[index])) { fastRemove(index); return true ; } } return false ; } private void fastRemove (int index) { Object destination[] = new Object[datas.length - 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); System.arraycopy(datas, index+1 , destination, index, datas.length - index-1 ); datas = destination; } @Override public boolean removeAll (T data) { return false ; } @Override public void clear () { datas = new Object[]{}; } @Override public String toString () { if (isEmpty()) return "" ; String str = "[" ; for (int i = 0 ; i<datas.length; i++){ str += (datas[i]+", " ); } str = str.substring(0 , str.lastIndexOf(", " )); return str+"]" ; } }
算法分析: 插入元素:删除元素: **
线性表的链式存储结构 顺序表必须占用一整块事先分配大小固定的存储空间,这样不便于存储空间的管理。为此提出了可以实现存储空间动态管理的链式存储方式–链表。
链表 在链式存储中,每个存储结点不仅包含元素本身的信息(数据域),还包含元素之间逻辑关系的信息,即一个结点中包含有直接后继结点的地址信息,这称为指针域。这样可以通过一个结点的指针域方便的找到后继结点的位置。 由于顺序表中每个元素至多只有一个直接前驱元素和一个直接后继元素。当采用链式存储时,一种最简单也最常用的方法是: 在每个结点中除包含数据域外,只设置一个指针域用以指向其直接后继结点,这种构成的链接表称为线性单向链接表,简称单链表。
另一种方法是,在每个结点中除包含数值域外,设置两个指针域,分别用以指向直接前驱结点和直接后继结点,这样构成的链接表称为线性双向链接表,简称双链表。
单链表当访问一个结点后,只能接着访问它的直接后继结点,而无法访问他的直接前驱结点。双链表则既可以依次向后访问每个结点,也可以依次向前访问每个结点。
单链表结点元素类型定义:
1 2 3 4 public class LNode { protected LNode next; protected Object data; }
双链表结点元素类型定义:
1 2 3 4 5 public class DNode { protected DNode prior; protected DNode next; protected Object data; }
在顺序表中,逻辑上相邻的元素,对应的存储位置也相邻,所以进行插入或删除操作时,通常需要平均移动半个表的元素,这是相当费时的操作。 在链表中,每个结点存储位置可以任意安排,不必要求相邻,插入或删除操作只需要修改相关结点的指针域即可,方便省时。对于单链表,如果要在结点p之前插入一个新结点,由于通过p并不能找到其前驱结点,我们需要从链表表头遍历至p的前驱结点然后进行插入操作,这样时间复杂度就是O(n),而顺序表插入删除结点时间复杂度也是O(n),那为什么说链表插入删除操作更加高效呢?因为单链表插入删除操作所消耗的时间主要在于查找前驱结点,这个查找工作的时间复杂度为O(n),而真正超如删除时间为O(1) 还有顺序表需要移动结点,移动结点通常比单纯的查找更加费时,链表不需要连续的空间,不需要扩容创建新表,所以同样时间复杂度O(n),链表更适合插入和删除操作。对于遍历查找前驱结点的问题,在双链表中就能很好的解决, 双链表在已知某结点的插入和删除操作时间复杂度是O(1)。 由于链表的每个结点带有指针域,从存储密度来讲,这是不经济的。所谓存储密度是指结点数据本身所占存储量和整改结点结构所占存储量之比。 3.2 单链表基本运算的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 public class LinkList <T > implements IList <T > { public LNode<T> head; public static <T> LinkList<T> createListF (T[] array) { LinkList llist = new LinkList(); if (array!=null && array.length>0 ) { for (T obj : array) { LNode<T> node = new LNode(); node.data = obj; node.next = llist.head; llist.head = node; } } return llist; } public static <T> LinkList<T> createListR (T[] array) { LinkList llist = new LinkList(); if (array!=null && array.length>0 ){ llist.head = new LNode(); llist.head.data = array[0 ]; LNode<T> temp = llist.head; for (int i = 1 ; i < array.length; i++){ LNode node = new LNode(); node.data = array[i]; temp.next = node; temp = node; } } return llist; } @Override public boolean isEmpty () { return head==null ; } @Override public int length () { if (head==null ) return 0 ; int l = 1 ; LNode node = head; while (node.next!=null ) { l++; node = node.next; } return l; } @Override public void clear () { head = null ; } @Override public T set (int index, T data) { return null ; } @Override public boolean contains (T data) { return false ; } @Override public T get (int index) { return getNode(index).data; } public LNode<T> getNode (int index) { LNode node = head; int j = 0 ; while (j < index && node!=null ){ j++; node = node.next; } return node; } @Override public int indexOf (T data) { if (head==null ) return -1 ; LNode node = head; int j = 0 ; while (node!=null ){ if (node.data.equals(data)) return j; j++; node = node.next; } return -1 ; } @Override public int lastIndexOf (T data) { if (head==null ) return -1 ; int index = -1 ; LNode node = head; int j = 0 ; while (node!=null ){ if (node.data.equals(data)) { index = j; } j++; node = node.next; } return index; } public LNode<T> getReNode (int k) { if (head==null ) return null ; int len = length(); if (k > len) return null ; LNode target = head; LNode next = head; for (int i=0 ;i < k;i++) next = next.next; while (next!=null ){ target = target.next; next = next.next; } return target; } public LNode getMiddleNode () { if (head == null || head.next == null ) return head; LNode target = head; LNode temp = head; while (temp != null && temp.next != null ){ target = target.next; temp = temp.next.next; } return target; } public static LNode mergeList (LNode head1, LNode head2) { if (head1==null ) return head2; if (head2==null ) return head1; LNode loop = head1; while (loop.next!=null ) loop = loop.next; loop.next = head2; return head1; } public static LNode mergeSortedListRec (LNode head1, LNode head2) { if (head1==null )return head2; if (head2==null )return head1; if (((int )head1.data)>((int )head2.data)) { head2.next = mergeSortedListRec(head2.next, head1); return head2; } else { head1.next = mergeSortedListRec(head1.next, head2); return head1; } } public void reverseListByLoop () { if (head == null || head.next == null ) return ; LNode pre = null ; LNode nex = null ; while (head != null ) { nex = head.next; head.next = pre; pre = head; head = nex; } head = pre; } public LNode reverseListByRec (LNode head) { if (head==null ||head.next==null ) return head; LNode reHead = reverseListByRec(head.next); head.next.next = head; head.next = null ; return reHead; } @Override public String toString () { if (head == null ) return "" ; LNode node = head; StringBuffer buffer = new StringBuffer(); while (node != null ){ buffer.append(node.data+" -> " ); node = node.next; } return buffer.toString(); } public static String display (LNode head) { if (head == null ) return "" ; LNode node = head; StringBuffer buffer = new StringBuffer(); while (node != null ){ buffer.append(" -> " +node.data); node = node.next; } return buffer.toString(); } public String displayReverseStack () { if (head == null ) return "" ; Stack <LNode> stack = new Stack < >(); LNode head = this .head; while (head!=null ){ stack.push(head); head=head.next; } StringBuffer buffer = new StringBuffer(); while (!stack.isEmpty()){ buffer.append(" -> " +stack.pop().data); } return buffer.toString(); } public void displayReverseRec (StringBuffer buffer, LNode head) { if (head==null ) return ; displayReverseRec(buffer, head.next); buffer.append(" -> " ); buffer.append(head.data); } @Override public boolean add (int index, T data) { if (index==0 ){ LNode temp = new LNode(); temp.next = head; return true ; } int j = 0 ; LNode node = head; while (j < index-1 && node!=null ){ j++; node = node.next; } if (node==null ) return false ; LNode temp = new LNode(); temp.data = data; temp.next = node.next; node.next = temp; return true ; } @Override public boolean add (T data) { LNode node = head; while (node!=null && node.next!=null ) node = node.next; LNode temp = new LNode(); temp.data = data; node.next = temp; return false ; } @Override public T remove (int index) { LNode<T> node = deleteNode(index); return node==null ?null :node.data; } public LNode deleteNode (int index) { LNode node = head; if (index==0 ){ if (node==null ) return null ; head = node.next; return node; } int j = 0 ; while (j < index-1 && node!=null ){ j++; node = node.next; } if (node==null ) return null ; LNode delete = node.next; if (delete==null ) return null ; node.next = delete.next; return delete; } @Override public boolean remove (T data) { return false ; } @Override public boolean removeAll (T data) { return false ; } public static void deleteNode (LNode head, LNode delete) { if (delete==null ) return ; if (delete.next==null ){ if (head==delete) head = null ; else { LNode temp = head; while (temp.next!=delete) temp = temp.next; temp.next=null ; } } else { delete.data = delete.next.data; delete.next = delete.next.next; } return ; } public static boolean hasCycle (LNode head) { LNode p1 = head; LNode p2 = head; while (p1!=null && p2!=null ){ p1 = p1.next; p2 = p2.next.next; if (p2 == p1) return true ; } return false ; } public static LNode getFirstNodeInCycleHashMap (LNode head) { LNode target = null ; HashMap<LNode,Boolean > map=new HashMap< >(); while (head != null ){ if (map.containsKey(head)) { target = head; break ; } else { map.put(head, true ); head = head.next; } } return target; } public static LNode getFirstNodeInCycle (LNode head) { LNode fast = head; LNode slow = head; while (fast != null && fast.next != null ){ slow = slow.next; fast = fast.next.next; if (slow == fast) break ; } if (fast == null ||fast.next == null ) return null ; slow=head; while (slow!=fast){ slow=slow.next; fast=fast.next; } return slow; } public static LNode isIntersect (LinkList list1, LinkList list2) { LNode head1 = list1.head; LNode head2 = list2.head; if (head1==null || head2==null )return null ; int len1 = list1.length(); int len2 = list2.length(); if (len1 >= len2){ for (int i=0 ;i < len1-len2;i++){ head1=head1.next; } }else { for (int i=0 ;i < len2-len1;i++){ head2=head2.next; } } while (head1 != null &&head2 != null ){ if (head1 == head2) return head1; head1=head1.next; head2=head2.next; } return null ; } }
算法分析
判断一个单链表中是否有环 我们可以通过HashMap判断,遍历结点,将结点值放入HashMap,如果某一刻发现当前结点在map中已经存在,则存在环,并且此结点正是环的入口,此算法见8.2方法。但是有一种问法是不通过任何其他数据结构怎么判断单链表是否存在环。 这样我们可利用的就只有单链表本身,一种解法是通过快慢指针,遍历链表,一个指针跳一步(慢指针步长为1),另一个指针跳两步(快指针步长为2),如果存在环,这两个指针必将在某一刻指向同一结点,假设此时慢指针跳了n步,则快指针跳的步数为n/2步:
判断两个单链表是否相交 由于单链表的特性(只有一个指针域),如果两个表相交,那必定是Y形相交,不会是X形相交,如图所示。两个单链表后面的结点相同,不同的部分只有前面,砍掉较长的链表的前面部分,然后两个链表同步遍历,必将指向同一个结点,这个结点就是交点:
双链表 双链表中每个结点有两个指针域,一个指向其直接后继结点,一个指向其直接前驱结点。 建立双链表也有两种方法,头插法和尾插法,这与创建单链表过程相似。在双链表中,有些算法如求长度、取元素值、查找元素等算法与单链表中相应算法是相同的。但是在单链表中,进行结点插入和删除时涉及前后结点的一个指针域的变化,而双链表中结点的插入和删除操作涉及前后结点的两个指针域的变化。java中LinkedList正是对双链表的实现,算法可参考此类。 双链表基本运算的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 public class DLinkList <T > implements IList <T > { transient DNode<T> first; transient DNode<T> last; private int size; public static <T> DLinkList<T> createListF (T[] array) { DLinkList dlist = new DLinkList(); if (array!=null && array.length>0 ) { dlist.size = array.length; for (T obj : array) { DNode<T> node = new DNode(); node.data = obj; node.next = dlist.first; if (dlist.first!=null ) dlist.first.prior = node; else dlist.last = node; dlist.first = node; } } return dlist; } public static <T> DLinkList<T> createListR (T[] array) { DLinkList dlist = new DLinkList(); if (array!=null && array.length>0 ){ dlist.size = array.length; dlist.first = new DNode<T>(); dlist.first.data = array[0 ]; dlist.last = dlist.first; for (int i = 1 ; i < array.length; i++){ DNode<T> node = new DNode(); node.data = array[i]; dlist.last.next = node; node.prior = dlist.last; dlist.last = node; } } return dlist; } @Override public boolean isEmpty () { return size==0 ; } @Override public int length () { return size; } @Override public boolean add (int index, T data) { if (index < 0 || index > size) throw new IndexOutOfBoundsException(); DNode<T> newNode = new DNode(); newNode.data = data; if (index == size) { final DNode<T> l = last; if (l == null ) first = newNode; else { l.next = newNode; newNode.prior = l; } last = newNode; size++; } else { DNode<T> indexNode = getNode(index); DNode<T> pred = indexNode.prior; newNode.prior = pred; newNode.next = indexNode; indexNode.prior = newNode; if (pred == null ) first = newNode; else pred.next = newNode; size++; } return false ; } @Override public boolean add (T data) { return add(size, data); } @Override public T remove (int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); return unlink(getNode(index)); } @Override public boolean remove (T data) { if (data == null ) { for (DNode<T> x = first; x != null ; x = x.next) { if (x.data == null ) { unlink(x); return true ; } } } else { for (DNode<T> x = first; x != null ; x = x.next) { if (data.equals(x.data)) { unlink(x); return true ; } } } return false ; } @Override public boolean removeAll (T data) { boolean result = false ; if (data == null ) { for (DNode<T> x = first; x != null ; x = x.next) { if (x.data == null ) { unlink(x); result = true ; } } } else { for (DNode<T> x = first; x != null ; x = x.next) { if (data.equals(x.data)) { unlink(x); result = true ; } } } return result; } private T unlink (DNode<T> x) { final T element = x.data; final DNode<T> next = x.next; final DNode<T> prev = x.prior; if (prev == null ) { first = next; } else { prev.next = next; x.prior = null ; } if (next == null ) { last = prev; } else { next.prior = prev; x.next = null ; } x.data = null ; size--; return element; } @Override public void clear () { for (DNode<T> x = first; x != null ; ) { DNode<T> next = x.next; x.data = null ; x.next = null ; x.prior = null ; x = next; } first = last = null ; size = 0 ; } @Override public T set (int index, T data) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); DNode<T> x = getNode(index); T oldVal = x.data; x.data = data; return oldVal; } @Override public boolean contains (T data) { return indexOf(data) != -1 ; } @Override public int indexOf (T data) { int index = 0 ; if (data == null ) { for (DNode<T> x = first; x != null ; x = x.next) { if (x.data == null ) return index; index++; } } else { for (DNode<T> x = first; x != null ; x = x.next) { if (data.equals(x.data)) return index; index++; } } return -1 ; } @Override public int lastIndexOf (T data) { int index = size; if (data == null ) { for (DNode<T> x = last; x != null ; x = x.prior) { index--; if (x.data == null ) return index; } } else { for (DNode<T> x = last; x != null ; x = x.prior) { index--; if (data.equals(x.data)) return index; } } return -1 ; } @Override public T get (int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); return getNode(index).data; } private DNode<T> getNode (int index) { if (index < (size >> 1 )) { DNode<T> x = first; for (int i = 0 ; i < index; i++) x = x.next; return x; } else { DNode<T> x = last; for (int i = size - 1 ; i > index; i--) x = x.prior; return x; } } public void reverse () { last = first; DNode now = first; DNode next; while (now!=null ){ next = now.next; now.next = now.prior; now.prior = next; first = now; now = next; } } @Override public String toString () { if (size == 0 ) return "" ; DNode node = first; StringBuffer buffer = new StringBuffer(); buffer.append(" " ); while (node != null ){ buffer.append(node.data+" -> " ); node = node.next; } buffer.append("next\npre" ); node = last; int start = buffer.length(); LogUtil.i(getClass().getSimpleName(), "buffer长度:" +buffer.length()); while (node != null ){ buffer.insert(start ," <- " +node.data); node = node.prior; } return buffer.toString(); } }
算法分析 双链表与单链表不同之处在于,双链表能从两端依次访问各个结点。单链表相对于顺序表优点是插入、删除数据更方便,但是访问结点需要遍历,时间复杂度为O(n); 双链表就是在单链表基础上做了一个优化,使得访问结点更加便捷(从两端),这样从近的一端出发时间复杂度变为O(n/2),虽然不是指数阶的区别,但也算是优化。双链表在插入、删除结点时逻辑比单链表稍麻烦:
循环链表 循环链表是另一种形式的链式存储结构,它的特点是表中尾结点的指针域不再是空,而是指向头结点,整个链表形成一个环。由此,从表中任意一结点出发均可找到链表中其他结点。如图所示为带头结点的循环单链表和循环双链表:
有序表 所谓有序表,是指所有结点元素值以递增或递减方式排列的线性表,并规定有序表中不存在元素值相同的结点。 有序表可以采用顺序表和链表进行存储,若以顺序表存储有序表,其算法除了add(T data)以外,其他均与前面说的顺序表对应的运算相同。有序表的add(T data)操作不是插入到末尾,而需要遍历比较大小后插入相应位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public boolean addByOrder (int data) { int index = 0 ; while (index<datas.length && (int )datas[index]<data) index++; if ((int )datas[index] == data) return false ; Object destination[] = new Object[datas.length + 1 ]; System.arraycopy(datas, 0 , destination, 0 , index); System.arraycopy(datas, index, destination, index+1 , datas.length-index); destination[index] = data; datas = destination; return true ; }
最后 想强调的是 由于顺序表结构的底层实现借助的就是数组,因此对于初学者来说,可以把顺序表完全等价为数组,但实则不是这样。数据结构是研究数据存储方式的一门学科,它囊括的都是各种存储结构,而数组只是各种编程语言中的基本数据类型,并不属于数据结构的范畴。