链表结构、栈、队列、递归行为、哈希表和有序表

前言

  • 从物理存储角度来看,只有两种结构:数组结构和链表结构
  1. 数组结构:就是在内存中开辟一块连续的空间,各个元素排排坐,不能超过开辟的空间长度;
  2. 链表结构:不预先开辟空间,各个元素不一定连续挨着,但是每个元素都有它下一个元素的"地址",通过地址找到下一位,这就是(单向)链表,像链条一样 一个链一个;
  • 其他的像栈,队列,树都是逻辑上的结构

链表结构(物理结构)

  • 单向链表
    一个链表中,每个节点有且只有它的下一个节点的引用
public class Node {
    public int value;
    public Node next;
    public Node(int data) {
        value = data;
    }
}
  • 双向链表
    一个链表中,每个节点有它的下一个节点的引用,也有它的上一个节点的引用
public class DoubleNode {
    public int value;
    public DoubleNode last;
    public DoubleNode next;

    public DoubleNode(int data) {
        value = data;
    }
}
  • 单链表如何反转
 public class Node {
        private int value;
        private Node next;

        public Node(int value) {
            this.value = value;
        }
    }

    //单链表反转
    private void One(Node head) {
        Node pre = null;
        Node next = null;

        while (head != null) {
            //记录位置
            next = head.next;
            //反向
            head.next = pre;
            pre = head;
            head = next;
        }
        return;
    }

  • 双链表如何反转
public class DoubleNode {
        private int value;
        private DoubleNode next;
        private DoubleNode last;

        public DoubleNode(int value) {
            this.value = value;
        }
    }

    //双链表反转
    private void Two(DoubleNode head) {
        DoubleNode next = null;
        DoubleNode pre = null;

        while (head != null) {
            next = head.next;
            head.next = pre;
            head.next = next;

            pre = head;
            head = next;
        }
        return;

    }

  • 单链表,删除指定值
  public class Node {
        private int value;
        private Node next;

        public Node(int value) {
            this.value = value;
        }
    }

private void three(Node head, int value) {
        //先删除头
        while (head != null) {
            if (head.value == value) {
                head = head.next;
            } else break;
        }
        //删除后面的
        Node pre = head;
        Node next = head;

        while (next != null) {
            if (next.value == value) {
                pre.next = head.next;
            } else {
                pre = next;
            }
            next = next.next;
        }
    }
  • 双链表,删除指定值
 public class DoubleNode {
        private int value;
        private DoubleNode next;
        private DoubleNode last;

        public DoubleNode(int value) {
            this.value = value;
        }
    }

 private void Four(DoubleNode head, int value) {
        while (head != null) {
            if (head.value == value) {
                head = head.next;
                head.last = null;
            } else break;
        }

        DoubleNode pre = null;
        DoubleNode cur = null;

        while (cur != null) {
            if (cur.value == value) {
                pre.next = cur.next;
                pre = cur.next.last;
            } else {
                pre = cur;
            }
            cur = cur.next;
        }
    }

栈和队列(逻辑结构)

栈和队列是逻辑上的结构,一般都是由双向链表实现

  • 栈:先进后出, 想象一下汉诺塔,弹夹
  • 队列:先进先出,FIFO, 想象一下超市排队结账
用双向链表实现栈和队列

核心是通过一个双向链表实现的,可以headPush,headPop,tailPush,tailPop
核心-双向链表

public class DoubleEndsQueue<E> {

        public class DoubleNode2<E> {
            private E value;
            private DoubleNode2<E> next;
            private DoubleNode2<E> last;

            public DoubleNode2(E value) {
                this.value = value;
            }

            public E getValue() {
                return value;
            }

            public void setValue(E value) {
                this.value = value;
            }

            public DoubleNode2<E> getNext() {
                return next;
            }

            public void setNext(DoubleNode2<E> next) {
                this.next = next;
            }

            public DoubleNode2<E> getLast() {
                return last;
            }

            public void setLast(DoubleNode2<E> last) {
                this.last = last;
            }
        }

        private DoubleNode2<E> head;
        private DoubleNode2<E> tail;

        //插入头部
        public void pushHead(E value) {
            DoubleNode2<E> node2 = new DoubleNode2<E>(value);
            if (head == null) {
                head = node2;
                tail = node2;
                return;
            }
            node2.setNext(head);
        }

        //插入尾部
        public void pushTail(E value) {
            DoubleNode2<E> node2 = new DoubleNode2<E>(value);
            if (tail == null) {
                head = node2;
                tail = node2;
                return;
            }
            tail.setNext(node2);
            node2.setLast(tail);
            tail = node2;
        }

        //弹出头部
        public E popHead() {
            if (head == null) {
                return null;
            }
            DoubleNode2<E> cur = head;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                head = head.getNext();
                head.setLast(null);
                cur.setNext(null);
            }
            return cur.getValue();
        }

        public E popTail() {
            if (tail == null) {
                return null;
            }
            DoubleNode2<E> cur = tail;
            if (tail == head) {
                tail = null;
                head = null;
            } else {
                tail = tail.getLast();
                tail.setNext(null);
                cur.setLast(null);
            }
            return cur.getValue();
        }
    }

//用双向链表实现栈
    //后进后出
    public class MyStack<E> {
        DoubleEndsQueue<E> linkedList = new DoubleEndsQueue<>();

        public void push(E value) {
            linkedList.pushHead(value);
        }

        public E pop() {
            return linkedList.popHead();
        }
    }
  • 队列
//用双向链表实现队列
    //主要先进先出
    public class MyQueue<E> {
        private DoubleEndsQueue<E> linkedList = new DoubleEndsQueue<>();

        public void offer(E value) {
            linkedList.pushHead(value);
        }

        public E poll() {
            return linkedList.popTail();
        }
    }
用数组实现栈和队列

  • 比较简单,直接维护一个offset就好
public class MyStack2<E> {
        Object[] arr;
        int size;
        int offer = -1;

        public void push(E element) {
            if (offer == size) return;

            arr[++offer] = element;
        }

        public E pop() {
            if (offer < 0) {
                return null;
            }
            E e = (E) arr[offer--];
            return e;
        }

        public MyStack2(int size) {
            this.size = size;
            arr = new Object[size];
        }
    }
  • 队列
    要考虑ring buffer的问题了,如果通过head和tail"追赶"的方式实现,略显复杂;
    添加一个变量length表示当前队列中有多少个元素,就简单很多
public class MyQueue2<E> {
        Object[] arr;
        int size;
        int length;
        int addIndex;
        int popIndex;

        public boolean offer(E element) {
            if (size == length) return false;

            length++;
            arr[addIndex] = element;

            addIndex = nextIndex(addIndex);
            return true;
        }

        public E poll() {
            if (length == 0) {
                return null;
            }
            length--;
            E e = (E) arr[popIndex];
            // popIndex++
            popIndex = nextIndex(popIndex);
            return e;
        }

        private int nextIndex(int index) {
            return index < size - 1 ? index + 1 : 0;
        }

        public MyQueue2(int size) {
            this.size = size;
            arr = new Object[size];
        }
    }
面试题

一、实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能更

1、pop、push、getMin操作的时间复杂度都是O(1)
2、设计的栈类型可以使用现成的栈结构

思路:准备两个栈,一个data栈,一个min栈。数据压data栈,min栈对比min栈顶元素,谁小加谁。这样的话data栈和min栈是同步上升的,元素个数一样多,且min栈的栈顶,是data栈所有元素中最小的那个。数据弹出data栈,我们同步弹出min栈,保证个数相等,切min栈弹出的就是最小值

package class02;
 
import java.util.Stack;
 
public class Code05_GetMinStack {
 
	public static class MyStack1 {
		private Stack<Integer> stackData;
		private Stack<Integer> stackMin;
 
		public MyStack1() {
			this.stackData = new Stack<Integer>();
			this.stackMin = new Stack<Integer>();
		}
 
		public void push(int newNum) {
		    // 当前最小栈为空,直接压入
			if (this.stackMin.isEmpty()) {
				this.stackMin.push(newNum);
			// 当前元素小于最小栈的栈顶,压入当前值
			} else if (newNum <= this.getmin()) {
				this.stackMin.push(newNum);
			}
			// 往数据栈中压入当前元素
			this.stackData.push(newNum);
		}
 
		public int pop() {
			if (this.stackData.isEmpty()) {
				throw new RuntimeException("Your stack is empty.");
			}
			int value = this.stackData.pop();
			if (value == this.getmin()) {
				this.stackMin.pop();
			}
			return value;
		}
 
		public int getmin() {
			if (this.stackMin.isEmpty()) {
				throw new RuntimeException("Your stack is empty.");
			}
			return this.stackMin.peek();
		}
	}
 
	public static class MyStack2 {
		private Stack<Integer> stackData;
		private Stack<Integer> stackMin;
 
		public MyStack2() {
			this.stackData = new Stack<Integer>();
			this.stackMin = new Stack<Integer>();
		}
 
		public void push(int newNum) {
			if (this.stackMin.isEmpty()) {
				this.stackMin.push(newNum);
			} else if (newNum < this.getmin()) {
				this.stackMin.push(newNum);
			} else {
				int newMin = this.stackMin.peek();
				this.stackMin.push(newMin);
			}
			this.stackData.push(newNum);
		}
 
		public int pop() {
			if (this.stackData.isEmpty()) {
				throw new RuntimeException("Your stack is empty.");
			}
			// 弹出操作,同步弹出,保证大小一致,只返回给用户data栈中的内容即可
			this.stackMin.pop();
			return this.stackData.pop();
		}
 
		public int getmin() {
			if (this.stackMin.isEmpty()) {
				throw new RuntimeException("Your stack is empty.");
			}
			return this.stackMin.peek();
		}
	}
 
	public static void main(String[] args) {
		MyStack1 stack1 = new MyStack1();
		stack1.push(3);
		System.out.println(stack1.getmin());
		stack1.push(4);
		System.out.println(stack1.getmin());
		stack1.push(1);
		System.out.println(stack1.getmin());
		System.out.println(stack1.pop());
		System.out.println(stack1.getmin());
 
		System.out.println("=============");
 
		MyStack1 stack2 = new MyStack1();
		stack2.push(3);
		System.out.println(stack2.getmin());
		stack2.push(4);
		System.out.println(stack2.getmin());
		stack2.push(1);
		System.out.println(stack2.getmin());
		System.out.println(stack2.pop());
		System.out.println(stack2.getmin());
	}
 
}

二、用俩栈实现队列
思路:(倒一次)用两个栈,一个pushStack负责push,一个popStack负责pop当pop的时候如果popStack为空则倒腾一次,把pushStack一个个弹入popStack

/**
* 两个栈实现队列
**/
package class02;
 
import java.util.Stack;
 
public class Code06_TwoStacksImplementQueue {
 
	public static class TwoStacksQueue {
		public Stack<Integer> stackPush;
		public Stack<Integer> stackPop;
 
		public TwoStacksQueue() {
			stackPush = new Stack<Integer>();
			stackPop = new Stack<Integer>();
		}
 
		// push栈向pop栈倒入数据
		private void pushToPop() {
			if (stackPop.empty()) {
				while (!stackPush.empty()) {
					stackPop.push(stackPush.pop());
				}
			}
		}
 
		public void add(int pushInt) {
			stackPush.push(pushInt);
			pushToPop();
		}
 
		public int poll() {
			if (stackPop.empty() && stackPush.empty()) {
				throw new RuntimeException("Queue is empty!");
			}
			pushToPop();
			return stackPop.pop();
		}
 
		public int peek() {
			if (stackPop.empty() && stackPush.empty()) {
				throw new RuntimeException("Queue is empty!");
			}
			pushToPop();
			return stackPop.peek();
		}
	}
 
	public static void main(String[] args) {
		TwoStacksQueue test = new TwoStacksQueue();
		test.add(1);
		test.add(2);
		test.add(3);
		System.out.println(test.peek());
		System.out.println(test.poll());
		System.out.println(test.peek());
		System.out.println(test.poll());
		System.out.println(test.peek());
		System.out.println(test.poll());
	}
 
}

三、用俩队列实现栈
思路:用两个队列,dataQ和helpQ 每次pop时倒腾一次,把dataQ[1~N]一个个弹入helpQ,留下dataQ[0]等会返回不入helpQ,然后交换dataQ和helpQ的引用

/**
* 两个队列实现栈 
**/
package class02;
 
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
 
public class Code07_TwoQueueImplementStack {
 
	public static class TwoQueueStack<T> {
		public Queue<T> queue;
		public Queue<T> help;
 
		public TwoQueueStack() {
			queue = new LinkedList<>();
			help = new LinkedList<>();
		}
 
		public void push(T value) {
			queue.offer(value);
		}
 
		public T poll() {
			while (queue.size() > 1) {
				help.offer(queue.poll());
			}
			T ans = queue.poll();
			Queue<T> tmp = queue;
			queue = help;
			help = tmp;
			return ans;
		}
 
		public T peek() {
			while (queue.size() > 1) {
				help.offer(queue.poll());
			}
			T ans = queue.poll();
			help.offer(ans);
			Queue<T> tmp = queue;
			queue = help;
			help = tmp;
			return ans;
		}
 
		public boolean isEmpty() {
			return queue.isEmpty();
		}
 
	}
 
	public static void main(String[] args) {
		System.out.println("test begin");
		TwoQueueStack<Integer> myStack = new TwoQueueStack<>();
		Stack<Integer> test = new Stack<>();
		int testTime = 1000000;
		int max = 1000000;
		for (int i = 0; i < testTime; i++) {
			if (myStack.isEmpty()) {
				if (!test.isEmpty()) {
					System.out.println("Oops");
				}
				int num = (int) (Math.random() * max);
				myStack.push(num);
				test.push(num);
			} else {
				if (Math.random() < 0.25) {
					int num = (int) (Math.random() * max);
					myStack.push(num);
					test.push(num);
				} else if (Math.random() < 0.5) {
					if (!myStack.peek().equals(test.peek())) {
						System.out.println("Oops");
					}
				} else if (Math.random() < 0.75) {
					if (!myStack.poll().equals(test.pop())) {
						System.out.println("Oops");
					}
				} else {
					if (myStack.isEmpty() != test.isEmpty()) {
						System.out.println("Oops");
					}
				}
			}
		}
 
		System.out.println("test finish!");
 
	}
 
}

递归

  • 递归思想:
    递归就是把一个大任务,不断的切分成几个子任务,
    然后每个子任务又切分成孙子任务…
    直到达到某个条件后,不再继续向下切分,向上返回.

  • 举个例子说明什么是递归:
    求数组arr[L…R]中的最大值,怎么用递归方法实现

//递归获取数组最大值
    public static int getMax(int[] arr) {
        if (arr.length == 0) return Integer.parseInt(null);

        return process(arr,0,arr.length-1);
    }

    private static int process(int[] arr, int L, int R) {
        if (L==R)return arr[L];

        int mid = L + ((R - L) >> 1); // 中点
        int maxL=process(arr,L,mid);
        int maxR=process(arr,mid+1,R);
        return Math.max(maxL,maxR);
    }
  • 递归的时间复杂度Master公式:

对于满足T(N) = aT(N/b) + O(N^d)其中: a,b,d为常数

公式表示,子问题的规模是一致的,该子问题调用了a次,N/b代表子问题的规模,O(N^d)为除去递归调用剩余的时间复杂度。

比如上述问题的递归,[L…R]上有N个数,第一个子问题的规模是N/2,第二个子问题的规模也是N/2。子问题调用了2次。额为复杂度为O(1),那么公式为:

T(N) = 2T(N/2) + O(N^0)

结论:如果我们的递归满足这种公式,那么该递归的时间复杂度(Master公式)为

logb^a > d   =>  O(N ^ (logb^a))
 
logb^a < d   =>  O(N^d)
 
logb^a == d   =>  O(N^d * logN)

那么上述问题的a=2, b=2,d=0,满足第一条,递归时间复杂度为:O(N)

哈希表

Hash表的增删改查,在使用的时候,一律认为时间复杂度是O(1)的

注意:在Java底层,包装类如果范围比较小,底层仍然采用值传递,比如Integer如果范围在-128~127之间,是按值传递的

但是在Hash表中,即使是包装类型的key,我们也一律按值传递,例如Hash<Integer,String>如果我们put相同的key的值,那么不会产生两个值相等的key而是覆盖操作。但是Hash表并不是一直是按值传递的,只是针对包装类型,如果是我们自定义的引用类型,那么仍然按引用传递

有序表

顺序表比哈希表功能多,但是顺序表的很多操作时间复杂度是O(logN)

有序表的底层可以有很多结构实现,比如AVL树,SB树,红黑树,跳表。其中AVL,SB,红黑都是具备各自平衡性的搜索二叉树

由于平衡二叉树每时每刻都会维持自身的平衡,所以操作为O(logN)。暂时理解,后面会单独整理

由于满足去重排序功能来维持底层树的平衡,所以如果是基础类型和包装类型的key直接按值来做比较,但是如果我们的key是自己定义的类型,那么我们要自己制定比较规则(比较器),用来让底层的树保持比较后的平衡

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值