数据结构与算法JC班-左程云第二节课笔记(链表结构、栈、队列、递归行为、哈希表)

第2节 链表结构、栈、队列、递归行为、哈希表

1、链表结构

(1)单向链表的定义

public class Node{
    public int value;
    public Node next;

    public Node(int data){
        value = data;
    }
}

(2)双向链表的定义

public class DoubleNode{
    public int value;
    public Node last;
    public Node next;

    public DoubleNode(int data){
        value = data;
    }
}

(3)单向链表和双向链表最简单的练习

链表相关的题基本上都是coding的题

  • 单链表和双链表的逆序问题
package class02;

import java.util.ArrayList;

public class Code01_ReverseList {

	public static class Node {
		public int value;
		public Node next;

		public Node(int data) {
			value = data;
		}
	}

	public static class DoubleNode {
		public int value;
		public DoubleNode last;
		public DoubleNode next;

		public DoubleNode(int data) {
			value = data;
		}
	}

	public static Node reverseLinkedList(Node head) {
		Node pre = null;
		Node next = null;
		while (head != null) {
			next = head.next;
			head.next = pre;
			pre = head;
			head = next;
		}
		return pre;
	}

	public static DoubleNode reverseDoubleList(DoubleNode head) {
		DoubleNode pre = null;
		DoubleNode next = null;
		while (head != null) {
			next = head.next;
			head.next = pre;
			head.last = next;
			pre = head;
			head = next;
		}
		return pre;
	}

	public static Node testReverseLinkedList(Node head) {
		if (head == null) {
			return null;
		}
		ArrayList<Node> list = new ArrayList<>();
		while (head != null) {
			list.add(head);
			head = head.next;
		}
		list.get(0).next = null;
		int N = list.size();
		for (int i = 1; i < N; i++) {
			list.get(i).next = list.get(i - 1);
		}
		return list.get(N - 1);
	}

	public static DoubleNode testReverseDoubleList(DoubleNode head) {
		if (head == null) {
			return null;
		}
		ArrayList<DoubleNode> list = new ArrayList<>();
		while (head != null) {
			list.add(head);
			head = head.next;
		}
		list.get(0).next = null;
		DoubleNode pre = list.get(0);
		int N = list.size();
		for (int i = 1; i < N; i++) {
			DoubleNode cur = list.get(i);
			cur.last = null;
			cur.next = pre;
			pre.last = cur;
			pre = cur;
		}
		return list.get(N - 1);
	}

	public static Node generateRandomLinkedList(int len, int value) {
		int size = (int) (Math.random() * (len + 1));
		if (size == 0) {
			return null;
		}
		size--;
		Node head = new Node((int) (Math.random() * (value + 1)));
		Node pre = head;
		while (size != 0) {
			Node cur = new Node((int) (Math.random() * (value + 1)));
			pre.next = cur;
			pre = cur;
			size--;
		}
		return head;
	}

	public static DoubleNode generateRandomDoubleList(int len, int value) {
		int size = (int) (Math.random() * (len + 1));
		if (size == 0) {
			return null;
		}
		size--;
		DoubleNode head = new DoubleNode((int) (Math.random() * (value + 1)));
		DoubleNode pre = head;
		while (size != 0) {
			DoubleNode cur = new DoubleNode((int) (Math.random() * (value + 1)));
			pre.next = cur;
			cur.last = pre;
			pre = cur;
			size--;
		}
		return head;
	}

	// 要求无环,有环别用这个函数
	public static boolean checkLinkedListEqual(Node head1, Node head2) {
		while (head1 != null && head2 != null) {
			if (head1.value != head2.value) {
				return false;
			}
			head1 = head1.next;
			head2 = head2.next;
		}
		return head1 == null && head2 == null;
	}

	// 要求无环,有环别用这个函数
	public static boolean checkDoubleListEqual(DoubleNode head1, DoubleNode head2) {
		boolean null1 = head1 == null;
		boolean null2 = head2 == null;
		if (null1 && null2) {
			return true;
		}
		if (null1 ^ null2) {
			return false;
		}
		if (head1.last != null || head2.last != null) {
			return false;
		}
		DoubleNode end1 = null;
		DoubleNode end2 = null;
		while (head1 != null && head2 != null) {
			if (head1.value != head2.value) {
				return false;
			}
			end1 = head1;
			end2 = head2;
			head1 = head1.next;
			head2 = head2.next;
		}
		if (head1 != null || head2 != null) {
			return false;
		}
		while (end1 != null && end2 != null) {
			if (end1.value != end2.value) {
				return false;
			}
			end1 = end1.last;
			end2 = end2.last;
		}
		return end1 == null && end2 == null;
	}

	public static void main(String[] args) {
		int len = 50;
		int value = 100;
		int testTime = 100000;
		for (int i = 0; i < testTime; i++) {
			Node node1 = generateRandomLinkedList(len, value);
			Node reverse1 = reverseLinkedList(node1);
			Node back1 = testReverseLinkedList(reverse1);
			if (!checkLinkedListEqual(node1, back1)) {
				System.out.println("oops!");
				break;
			}
			DoubleNode node2 = generateRandomDoubleList(len, value);
			DoubleNode reverse2 = reverseDoubleList(node2);
			DoubleNode back2 = testReverseDoubleList(reverse2);
			if (!checkDoubleListEqual(node2, back2)) {
				System.out.println("oops!");
				break;
			}
		}
		System.out.println("finish!");

	}

}
  • 删除单链表中的某个数字问题
package class02;

public class Code02_DeleteGivenValue {

	public static class Node {
		public int value;
		public Node next;

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

	public static Node removeValue(Node head, int num) {
		while (head != null) {
			if (head.value != num) {
				break;
			}
			head = head.next;
		}
		// head来到 第一个不需要删的位置
		Node pre = head;
		Node cur = head;
		// 
		while (cur != null) {
			if (cur.value == num) {
				pre.next = cur.next;
			} else {
				pre = cur;
			}
			cur = cur.next;
		}
		return head;
	}

}
  • 扩展:Java的代码也会产生内存泄露。JVM会怎样释放空间?

如果你现在正在生存某个变量或者正在引用某个变量,JVM不会把这个变量释放掉,如果JVM找不到活着的引用的内存,JVM会把它释放(如下图所示,即便你没有把1杀死,也没有把1→2的引用去除,JVM会认为,你再怎么也无法到达1,就会自动帮助你毁灭)。
数据结构与算法JC班-左程云第二节课笔记(链表结构、栈、队列、递归行为、哈希表)_第1张图片

2、栈和队列

栈:先进后出

队列:先进先出

(1)怎么实现这两种逻辑结构呢?

  • 利用双链表结构实现。
package class02;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class Code03_DoubleEndsQueueToStackAndQueue {

	public static class Node<T> {
		public T value;
		public Node<T> last;
		public Node<T> next;

		public Node(T data) {
			value = data;
		}
	}

	public static class DoubleEndsQueue<T> {
		public Node<T> head;
		public Node<T> tail;

		public void addFromHead(T value) {
			Node<T> cur = new Node<T>(value);
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				cur.next = head;
				head.last = cur;
				head = cur;
			}
		}

		public void addFromBottom(T value) {
			Node<T> cur = new Node<T>(value);
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				cur.last = tail;
				tail.next = cur;
				tail = cur;
			}
		}

		public T popFromHead() {
			if (head == null) {
				return null;
			}
			Node<T> cur = head;
			if (head == tail) {
				head = null;
				tail = null;
			} else {
				head = head.next;
				cur.next = null;
				head.last = null;
			}
			return cur.value;
		}

		public T popFromBottom() {
			if (head == null) {
				return null;
			}
			Node<T> cur = tail;
			if (head == tail) {
				head = null;
				tail = null;
			} else {
				tail = tail.last;
				tail.next = null;
				cur.last = null;
			}
			return cur.value;
		}

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

	}

	public static class MyStack<T> {
		private DoubleEndsQueue<T> queue;

		public MyStack() {
			queue = new DoubleEndsQueue<T>();
		}

		public void push(T value) {
			queue.addFromHead(value);
		}

		public T pop() {
			return queue.popFromHead();
		}

		public boolean isEmpty() {
			return queue.isEmpty();
		}

	}

	public static class MyQueue<T> {
		private DoubleEndsQueue<T> queue;

		public MyQueue() {
			queue = new DoubleEndsQueue<T>();
		}

		public void push(T value) {
			queue.addFromHead(value);
		}

		public T poll() {
			return queue.popFromBottom();
		}

		public boolean isEmpty() {
			return queue.isEmpty();
		}

	}

	public static boolean isEqual(Integer o1, Integer o2) {
		if (o1 == null && o2 != null) {
			return false;
		}
		if (o1 != null && o2 == null) {
			return false;
		}
		if (o1 == null && o2 == null) {
			return true;
		}
		return o1.equals(o2);
	}

	public static void main(String[] args) {
		int oneTestDataNum = 100;
		int value = 10000;
		int testTimes = 100000;
		for (int i = 0; i < testTimes; i++) {
			MyStack<Integer> myStack = new MyStack<>();
			MyQueue<Integer> myQueue = new MyQueue<>();
			Stack<Integer> stack = new Stack<>();
			Queue<Integer> queue = new LinkedList<>();
			for (int j = 0; j < oneTestDataNum; j++) {
				int nums = (int) (Math.random() * value);
				if (stack.isEmpty()) {
					myStack.push(nums);
					stack.push(nums);
				} else {
					if (Math.random() < 0.5) {
						myStack.push(nums);
						stack.push(nums);
					} else {
						if (!isEqual(myStack.pop(), stack.pop())) {
							System.out.println("oops!");
						}
					}
				}
				int numq = (int) (Math.random() * value);
				if (stack.isEmpty()) {
					myQueue.push(numq);
					queue.offer(numq);
				} else {
					if (Math.random() < 0.5) {
						myQueue.push(numq);
						queue.offer(numq);
					} else {
						if (!isEqual(myQueue.poll(), queue.poll())) {
							System.out.println("oops!");
						}
					}
				}
			}
		}
		System.out.println("finish!");
	}

}
  • 利用数组结构实现。
package class02;

public class Code04_RingArray {

	public static class MyQueue {
		private int[] arr;
		private int pushi;
		private int polli;
		private int size;
		private final int limit;

		public MyQueue(int limit) {
			arr = new int[limit];
			pushi = 0;
			polli = 0;
			size = 0;
			this.limit = limit;
		}

		public void push(int value) {
			if (size == limit) {
				throw new RuntimeException("栈满了,不能再加了");
			}
			size++;
			arr[pushi] = value;
			pushi = nextIndex(pushi);
		}

		public int pop() {
			if (size == 0) {
				throw new RuntimeException("栈空了,不能再拿了");
			}
			size--;
			int ans = arr[polli];
			polli = nextIndex(polli);
			return ans;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		// 如果现在的下标是i,返回下一个位置
		private int nextIndex(int i) {
			return i < limit - 1 ? i + 1 : 0;
		}

	}

}

(2)既然语言都有这些结构和api,为什么还需要手撸练习?

  • 算法问题无关语言;
  • 语言提供的api是有限的,当有新的功能是api不提供的,就需要改写;
  • 任何软件工具的底层都是最基本的算法和数据结构,这是绕不过去的。

(3)栈和队列的常见面试题

  • 怎么用数组实现不超过固定大小的队列和栈?栈:正常使用;队列:环形数组。已经实现了!
  • 实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能。pop、push、getMin操作的时间复杂度都是O(1),设计的栈类型可以使用现成的栈结构。
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.");
			}
			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());
	}

}
  • 如何用栈结构实现队列结构?
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());
	}

}
  • 如何用队列结构实现栈结构?
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!");

	}

}

3、递归

递归?这东西是什么啊!?

  • 怎么从思想上理解递归?
  • 怎么从实际实现的角度出发理解递归?
package class02;

public class Code08_GetMax {

	// 求arr中的最大值
	public static int getMax(int[] arr) {
		return process(arr, 0, arr.length - 1);
	}

	// arr[L..R]范围上求最大值  L ... R   N
	public static int process(int[] arr, int L, int R) {
		if (L == R) { // arr[L..R]范围上只有一个数,直接返回,base case
			return arr[L];
		}
		int mid = L + ((R - L) >> 1); // 中点   	1
		int leftMax = process(arr, L, mid);
		int rightMax = process(arr, mid + 1, R);
		return Math.max(leftMax, rightMax);
	}

}

递归的时间复杂度的经验公式(两个条件:子问题的规模一致;常数项规模可以表达为O(N^d))推导:a表示子问题调动了a次,N/b表示子问题的规模。

数据结构与算法JC班-左程云第二节课笔记(链表结构、栈、队列、递归行为、哈希表)_第2张图片

别管这个怎么推导的,时间复杂度你必须会推导。以上述代码为例,子问题的规模为N/2,可以得知b参数为2;此外,a为2,因为一轮中执行了两次子问题,最后,d为0,因为有一个return max,这个是常数时间。所以适合第一个式子,也就是:时间复杂度为O(N)。

4、哈希表和有序表的用法

哈希表使用示范(哈希表增删改查的时间复杂度都是O(1),很不可思议吧!你没必要知道它的原理是什么)

package class02;

import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;

public class HashMapAndSortedMap {
	
	
	public static class Node{
		public int value;
		public Node(int v) {
			 value = v;
		}
	}
	
	public static void main(String[] args) {
		// UnSortedMap
		HashMap<Integer, String> map = new HashMap<>();
		map.put(1000000, "我是1000000");
		map.put(2, "我是2");
		map.put(3, "我是3");
		map.put(4, "我是4");
		map.put(5, "我是5");
		map.put(6, "我是6");
		map.put(1000000, "我是1000001");
		
		System.out.println(map.containsKey(1));
		System.out.println(map.containsKey(10));
		
		System.out.println(map.get(4));
		System.out.println(map.get(10));
		
		map.put(4, "他是4");
		System.out.println(map.get(4));
		
		map.remove(4);
		System.out.println(map.get(4));
		
		
		
		//       key
		HashSet<String>  set = new HashSet<>();
		set.add("abc");
		set.contains("abc");
		set.remove("abc");
		
		// 哈希表,增、删、改、查,在使用时,O(1)
		
		System.out.println("=====================");
		int a = 100000;
		int b = 100000;
		System.out.println(a == b);
		
		Integer c = 100000;
		Integer d = 100000;
		// 如果直接使用c == d,比较的是地址值;入如果想判断值是否相等,直接用equals方法比较即可。
		System.out.println(c.equals(d));
		
		Integer e = 127;  //  - 128  ~  127
		Integer f = 127;
		// 注意哦!这个部分在Javaguide里面有,可见是没有白学习呀!
		System.out.println(e == f);

		// 非基础类型的key是引用传递
		HashMap<Node, String> map2 = new HashMap<>();
		Node node1 = new Node(1);
		Node node2 = node1;
		map2.put(node1, "我是node1");
		map2.put(node2, "我是node1");
		System.out.println(map2.size());

有序表使用示范(有序表增删改查的时间复杂度都是O(logN))

package class02;

import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;

public class HashMapAndSortedMap {
	
	
	public static class Node{
		public int value;
		public Node(int v) {
			 value = v;
		}
	}
	
	public static void main(String[] args) {
		
		TreeMap<Integer, String> treeMap = new TreeMap<>();
		
		treeMap.put(3, "我是3");
		treeMap.put(4, "我是4");
		treeMap.put(8, "我是8");
		treeMap.put(5, "我是5");
		treeMap.put(7, "我是7");
		treeMap.put(1, "我是1");
		treeMap.put(2, "我是2");

		System.out.println(treeMap.containsKey(1));
		System.out.println(treeMap.containsKey(10));
		
		System.out.println(treeMap.get(4));
		System.out.println(treeMap.get(10));
		
		treeMap.put(4, "他是4");
		System.out.println(treeMap.get(4));
		
		treeMap.remove(4);
		System.out.println(treeMap.get(4));
		
		// 这两个功能哈希表示没有的
		System.out.println(treeMap.firstKey());
		System.out.println(treeMap.lastKey());

		// <= 4,离4最近的key是哪个key(从左边看)
		System.out.println(treeMap.floorKey(4));
		// >= 4,离4最近的key是哪个key(从右边看)
		System.out.println(treeMap.ceilingKey(4));
		// O(logN)		
	}
}

我们用有序表去实现String,去实现基础类型啊都可以,但是如果放入Node类型的可以吗?有序表它需要比较呀!所以我们必须告诉有序表去怎么比较,这一点我们在堆的那里讲述,是关于比较器的。

你可能感兴趣的:(数据结构与算法基础班-左程云,链表,数据结构,算法)