线性结构是最简单,也是最常用的数据结构之一。线性结构的特点是:在数据元素的有限集中,除第一个元素无直接前驱,最后一个元素无直接后继以外,每个数据元素有且仅有一个直接前驱元素和一个直接后续元素。

数组

  1. /**
  2. * 二次封装属于我们自己的数组的准备工作
  3. */
  4. public class Array {
  5. private int[] data;
  6. private int size;
  7. public Array(int capacity){
  8. data=new int[capacity];
  9. size=0;
  10. }
  11. //无参构造函数,默认数组的容量是10
  12. public Array(){
  13. this(10);
  14. }
  15. public int getSize(){
  16. return size;
  17. }
  18. public int getCapacity(){
  19. return data.length;
  20. }
  21. public boolean isEmpty(){
  22. return size==0;
  23. }
  24. @Override
  25. public String toString(){
  26. StringBuilder builder=new StringBuilder();
  27. builder.append(String.format("Array size=%d,capacity=%d\n",size,data.length));
  28. builder.append("[");
  29. for(int i=0;i<size;i++){
  30. builder.append(data[i]);
  31. if(i!=size-1){
  32. builder.append(", ");
  33. }
  34. }
  35. builder.append("]");
  36. return builder.toString();
  37. }
  38. }

添加元素

  1. //在所有元素后添加新元素
  2. public void addLast(int e) {
  3. //要先判断是否有空间来容纳新元素
  4. /*if(size==data.length){
  5. throw new IllegalArgumentException("AddLast failed,array is full");
  6. }
  7. data[size]=e;
  8. size++;*/
  9. add(size,e);
  10. }
  11. //在所有元素前添加新元素
  12. public void addFirst(int e){
  13. add(0,e);
  14. }
  15. //在index位置插入元素
  16. public void add(int index,int e){
  17. if(size==data.length){
  18. throw new IllegalArgumentException("Add failed,array is full");
  19. }
  20. if(index<0 || index>size){
  21. throw new IllegalArgumentException("Add Failed,0<=index<=size is required");
  22. }
  23. //index位置后的元素向右移动
  24. for(int i=size;i>index;i--){
  25. data[i]=data[i-1];
  26. }
  27. data[index]=e;
  28. size++;
  29. }

查询元素

  1. //获取index位置的元素
  2. public int get(int index){
  3. if(index<0 || index>=size){
  4. throw new IllegalArgumentException("Get failed,index is illegal");
  5. }
  6. return data[index];
  7. }
  1. //查找数组中是否有元素e,有就返回下标,没有就返回-1
  2. public boolean contains(int e){
  3. for(int i=0;i<size;i++){
  4. if(data[i]==e){
  5. return true;
  6. }
  7. }
  8. return false;
  9. }
  10. //查找数组中元素e所在索引
  11. public int find(int e){
  12. for(int i=0;i<size;i++){
  13. if(data[i]==e){
  14. return i;
  15. }
  16. }
  17. return -1;
  18. }

修改元素

  1. //设置index位置元素值为e
  2. public void set(int index,int e){
  3. if(index<0 || index>=size){
  4. throw new IllegalArgumentException("Get failed,index is illegal");
  5. }
  6. data[index]=e;
  7. }

删除元素

  1. //删除指定位置元素
  2. public int remove(int index){
  3. if(size==0){
  4. throw new IllegalArgumentException("Remove failed,array is empty");
  5. }
  6. if(index<0 || index>=size){
  7. throw new IllegalArgumentException("Remove failed,index is illegal");
  8. }
  9. int ret=data[index];
  10. for(int i=index+1;i<size;i++){
  11. data[i-1]=data[i];
  12. }
  13. size--;
  14. return ret;
  15. }
  16. public int removeFirst(){
  17. return remove(0);
  18. }
  19. public int removeLast(){
  20. return remove(size-1);
  21. }
  22. public void removeElement(int e){
  23. int index=find(e);
  24. if(index!=-1){
  25. remove(index);
  26. }
  27. }

动态数组

调整数组大小

思路

  • 准备一个新数组,长度是原来数组的2倍

image.png

  • 将原来数组中的元素复制到新数组中

image.png

  • 将data指向newData,完成扩容

image.png

  • 完成扩容,capacity是原来的2倍,size不变,数组中数据不变

image.png

代码

  1. //动态调整数组大小
  2. private void resize(int newCapacity){
  3. E[] newData=(E[])new Object[newCapacity];
  4. for(int i=0;i<size;i++){
  5. newData[i]=data[i];
  6. }
  7. data=newData;
  8. }

添加元素

  1. //在index位置插入元素
  2. public void add(int index,E e){
  3. /* if(size==data.length){
  4. throw new IllegalArgumentException("Add failed,array is full");
  5. }*/
  6. if(size==data.length){
  7. resize(data.length*2);
  8. }
  9. if(index<0 || index>size){
  10. throw new IllegalArgumentException("Add Failed,0<=index<=size is required");
  11. }
  12. //index位置后的元素向右移动
  13. for(int i=size;i>index;i--){
  14. data[i]=data[i-1];
  15. }
  16. data[index]=e;
  17. size++;
  18. }

删除元素

  1. //删除指定位置元素
  2. public int remove(int index){
  3. /*if(size==0){
  4. throw new IllegalArgumentException("Remove failed,array is empty");
  5. }*/
  6. if(size==data.length/2){
  7. resize(data.length/2);
  8. }
  9. if(index<0 || index>=size){
  10. throw new IllegalArgumentException("Remove failed,index is illegal");
  11. }
  12. E ret=data[index];
  13. for(int i=index+1;i<size;i++){
  14. data[i-1]=data[i];
  15. }
  16. size--;
  17. data[size]=null;//loitering objects
  18. return ret;
  19. }

addLast() 时间复杂度分析

假设 capacity=n,(n+1)次进行 addLast 操作,会触发 resize,总共进行(2*n+1)次基本操作。

平均情况下,每次 addLast 操作,进行 2 次基本操作。这样均摊计算,时间复杂度是 O(1)。所以addLast的均摊复杂度是 O(1)。同理,removeLast 的均摊复杂度是O(1)。

复杂度震荡

capcity 为 n,此时 size=n:

进行 addLast() 操作,由于需要 resize(),时间复杂度为 O(n);

再进行 removeLast() 操作,由于需要 resize(),时间复杂度为 O(n);

再进行 addLast() 操作,由于需要 resize(),时间复杂度为 O(n)

这就是复杂度震荡。

解决复杂度震荡的方法:lazy,即在 size==capacity/4,才将 capacity 减半。

  1. //删除指定位置元素
  2. public int remove(int index){
  3. /*if(size==0){
  4. throw new IllegalArgumentException("Remove failed,array is empty");
  5. }*/
  6. /* if(size==data.length/2){
  7. resize(data.length/2);
  8. }*/
  9. if(size==data.length/4 && data.length/2!=0){
  10. resize(data.length/2);
  11. }
  12. if(index<0 || index>=size){
  13. throw new IllegalArgumentException("Remove failed,index is illegal");
  14. }
  15. E ret=data[index];
  16. for(int i=index+1;i<size;i++){
  17. data[i-1]=data[i];
  18. }
  19. size--;
  20. //data[size]=null;//loitering objects,使用泛型时,进行此操作
  21. return ret;
  22. }

时间复杂度分析

操作 时间复杂度
添加操作 平均时间复杂度:O(n)
addLast(e) O(1)
addFirst(e) O(n)
add(index) O(n)
删除操作 平均时间复杂度:O(n)
removeLast(e) O(1)
removeFirst(e) O(n)
remove(index,e) O(n)
修改操作
set(index,e) O(1)
查找操作
get(index) O(1)
contains(e) O(n)
find(e) O(n)

链表

链表结点

  1. /**
  2. * 链表结点
  3. */
  4. public class Node<E> {
  5. public E e;
  6. public Node next;
  7. public Node(E e, Node next){
  8. this.e=e;
  9. this.next=next;
  10. }
  11. public Node(E e){
  12. this(e,null);
  13. }
  14. public Node(){
  15. this(null,null);
  16. }
  17. @Override
  18. public String toString() {
  19. return e.toString();
  20. }
  21. }
  1. public class LinkedList<E> {
  2. private Node head;
  3. private int size;
  4. public LinkedList(){
  5. head=null;
  6. size=0;
  7. }
  8. public boolean isEmpty(){
  9. return size==0;
  10. }
  11. public int getSize(){
  12. return size;
  13. }
  14. @Override
  15. public String toString() {
  16. StringBuilder builder=new StringBuilder();
  17. Node cur=dummyHead.next;
  18. while(cur!=null){
  19. builder.append(cur+"->");
  20. cur=cur.next;
  21. }
  22. builder.append("NULL");
  23. return builder.toString();
  24. }
  25. }

插入元素

在链表头插入元素

  1. //在链表头添加元素
  2. public void addFirst(E e){
  3. Node node=new Node(e);
  4. node.next=head;
  5. head=node;
  6. size ++;
  7. }

在链表中间插入元素

  1. //在链表index位置[从0开始]插入元素
  2. //这项操作在链表中并不常用
  3. public void add(int index,E e){
  4. if(index<0 || index>=size){
  5. throw new IllegalArgumentException("Index is illegal");
  6. }
  7. if(index==0){
  8. addFirst(e);
  9. }else{
  10. Node prev=head;
  11. for(int i=0;i<index-1;i++){
  12. prev=prev.next;
  13. }
  14. Node node=new Node(e);
  15. node.next=prev.next;
  16. prev.next=node;
  17. size ++;
  18. }
  19. }

虚拟头结点

  1. public LinkedList(){
  2. dummyHead=new Node(null,null);
  3. size=0;
  4. }
  5. //在链表index位置[从0开始]插入元素
  6. //这项操作在链表中并不常用
  7. public void add(int index,E e){
  8. if(index<0 || index>size){
  9. throw new IllegalArgumentException("Index is illegal");
  10. }
  11. Node prev=dummyHead;
  12. for(int i=0;i<index;i++){
  13. prev=prev.next;
  14. }
  15. Node node=new Node(e);
  16. node.next=prev.next;
  17. prev.next=node;
  18. size++;
  19. }
  20. //在链表头添加元素
  21. public void addFirst(E e){
  22. add(0,e);
  23. }
  24. public void addLast(E e){
  25. add(size,e);
  26. }

查询元素

  1. //获取链表index位置[从0开始]元素
  2. //这项操作在链表中并不常用
  3. public E get(int index){
  4. if(index<0 || index>=size){
  5. throw new IllegalArgumentException("Index is illegal");
  6. }
  7. Node cur=dummyHead.next;
  8. for(int i=0;i<index;i++){
  9. cur=cur.next;
  10. }
  11. return (E)cur.e;
  12. }
  13. public E getFirst(){
  14. return get(0);
  15. }
  16. public E getLast(){
  17. return get(size-1);
  18. }
  1. //查找链表中是否有元素e
  2. public boolean contains(E e){
  3. Node cur=dummyHead.next;
  4. while(cur!=null){
  5. if(cur.e.equals(e)){
  6. return true;
  7. }
  8. cur=cur.next;
  9. }
  10. return false;
  11. }

修改元素

  1. //修改链表index位置[从0开始]元素
  2. public void set(int index,E e){
  3. if(index<0 || index>=size){
  4. throw new IllegalArgumentException("Index is illegal");
  5. }
  6. Node cur=dummyHead.next;
  7. for(int i=0;i<index;i++){
  8. cur=cur.next;
  9. }
  10. cur.e=e;
  11. }

删除元素

  1. //删除链表index位置[从0开始]元素
  2. public E remove(int index){
  3. if(index<0 || index>=size){
  4. throw new IllegalArgumentException("Index is illegal");
  5. }
  6. Node prev=dummyHead;
  7. for(int i=0;i<index;i++){
  8. prev=prev.next;
  9. }
  10. Node delNode= prev.next;
  11. prev.next=delNode.next;
  12. delNode.next=null;
  13. size--;
  14. return (E)delNode.e;
  15. }
  16. public E removeFirst(){
  17. return remove(0);
  18. }
  19. public E removeLast(){
  20. return remove(size-1);
  21. }

时间复杂度分析

操作 时间复杂度
添加操作 平均时间复杂度:O(n)
addlast() O(n)
addFirst() O(1)
add(index,e) O(n/2)=O(n)
删除操作 平均时间复杂度:O(n)
removeLast() O(n)
removeFirst() O(1)
remove(index,e) O(n/2)=O(n)
修改操作 O(n)
set(index,e) O(n)
查找操作 O(n)
get(index) O(n)
contains(e) O(n)