1. 顺序表
1.1 概念
- 线性表是最常见最基本、最简单的数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。
- 前驱元素:若A元素在B元素前面,则称A为B的前驱元素。
- 后继元素:若B元素在A元素后面,则称B为A的后继元素。
- 线性表的特征:数据元素之间具有一种“一对一”的逻辑关系。
- 第一个数据元素没有前驱,这个数据元素被称为头结点。
- 最后一个元素没有后继,这个元素被称为尾结点。
- 除了第一个和最后一个元素之外,其他的元素有且仅有一个前驱和一个后继。
- 线性表的分类:线性表中存储数据可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。
1.2 顺序表
- 顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组连续的存储单元,依次存储线性表中的各个元素,使得线性表中在逻辑上相邻的数据元素在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
- 顺序表的API设计:
- 顺序表的代码实现:
package com.tiger.study.DataStructure.Linear;
public class SequenceList<T>{
private T[] eles;
private int N;
public SequenceList(int capacity) {
this.eles = (T[]) new Object[capacity];
this.N = 0;
}
public void clear() {
this.N = 0;
}
public boolean isEmpty() {
return N == 0;
}
public int length() {
return N;
}
public T get(int i) {
return eles[i];
}
public void insert(T t) {
if (N == eles.length) {
resize(eles.length * 2);
}
eles[N++] = t;
}
public void insert(int i, T t) {
if (N == eles.length) {
resize(eles.length * 2);
}
for (int index = N; index > i; index--) {
eles[index] = eles[index - 1];
}
eles[i] = t;
N++;
}
public T remove(int i) {
T current = eles[i];
for (int index=i; index < N - 1; index++) {
eles[index] = eles[index+1];
}
N--;
if (N >0 && N < eles.length / 4) {
resize(eles.length/2);
}
return current;
}
public int indexOf(T t) {
for (int i = 0; i < N; i++) {
if (eles[i].equals(t)) {
return i;
}
}
return -1;
}
public void resize(int newSize) {
T[] temp = eles;
eles = (T[]) new Object[newSize];
for (int i = 0; i < N; i++) {
eles[i] = temp[i];
}
}
}
1.2 链表
- 链表是一种物理存储单元上非连续、非顺序的存储结构。链表中的数据元素的逻辑顺序是通过链表中的指针依次链接实现的。链表由一系列结点(链表中每个元素称为结点)组成,结点可以在运行时动态生成。
1.2.1 单项链表
- 单项链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头节点的数据域不存储数据,指针域指向第一个存储数据的结点。
- 单链表的API设计
- 单链表代码实现
package com.tiger.study.DataStructure.Linear;
import java.util.Iterator;
public class LinkList<T> implements Iterable<T>{
private Node head;
private int N;
private class Node {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public LinkList() {
this.head = new Node(null, null);
this.N = 0;
}
public void clear() {
head.next = null;
this.N = 0;
}
public int length() {
return N;
}
public boolean isEmpty() {
return N == 0;
}
public T get(int i) {
Node n = head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
public void insert(T t) {
Node n = head;
while (n.next != null) {
n = n.next;
}
n.next = new Node(t, null);
N++;
}
public void insert(int i, T t) {
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
Node curr = pre.next;
Node newNode = new Node(t, curr);
pre.next = newNode;
N++;
}
public T remove(int i) {
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
Node curr = pre.next;
Node nextNode = curr.next;
pre.next = nextNode;
N--;
return curr.item;
}
public int indexOf(T t) {
Node n = head;
for (int i = 0; n.next != null; i++) {
n = n.next;
if (n.item.equals(t)) {
return i;
}
}
return -1;
}
public void reverse() {
if (isEmpty()) {
return;
}
reverse(head.next);
}
public Node reverse(Node curr) {
if (curr.next == null) {
head.next = curr;
return curr;
}
Node pre = reverse(curr.next);
pre.next = curr;
curr.next = null;
return curr;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator{
private Node n;
public LIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
1.2.2 双向链表
- 双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向后继结点,另一个指针域用来指向前驱结点。链表的头节点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继节点的指针域指向第一个真正存储数据的结点。
- 双向链表的API设计
- 双向链表的代码实现:
package com.tiger.study.DataStructure.Linear;
import java.util.Iterator;
public class TwoWayLinkList<T> implements Iterable<T> {
private Node head;
private Node last;
private int N;
private class Node {
public T item;
public Node pre;
public Node next;
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
}
public TwoWayLinkList() {
this.head = new Node(null, null, null);
this.last = null;
this.N = 0;
}
public void clear() {
this.head.next = null;
this.head.pre = null;
this.head.item = null;
this.last = null;
this.N = 0;
}
public int length() {
return N;
}
public boolean isEmpty() {
return N == 0;
}
public T getFirst() {
if (isEmpty()) {
return null;
}
return head.next.item;
}
public T getLast() {
if (isEmpty()) {
return null;
}
return last.item;
}
public void insert(T t) {
if (isEmpty()) {
Node node = new Node(t, head, null);
last = node;
head.next = last;
} else {
Node oldLast = last;
Node node = new Node(t, oldLast, null);
oldLast.next = node;
last = node;
}
N++;
}
public void insert(int i, T t) {
Node pre = head;
for (int j = 0; j < i; j++) {
pre = pre.next;
}
Node curr = pre.next;
Node node = new Node(t, pre, curr);
pre.next = node;
curr.pre = node;
N++;
}
public T get(int i) {
Node n = head.next;
for (int j = 0; j < i; j++) {
n = n.next;
}
return n.item;
}
public int indexOf(T t) {
Node n = head;
for (int i = 0; n.next != null; i++) {
n = n.next;
if (n.next.equals(t)) {
return i;
}
}
return -1;
}
public T remove(int i) {
Node pre = head;
for (int j = 0; j < i; j++) {
pre = pre.next;
}
Node cur = pre.next;
pre.next = cur.next;
cur.next.pre = pre;
N--;
return cur.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private class TIterator implements Iterator {
private Node n;
public TIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
1.2.4 链表的反转
- 需求:将一个链表反转
- 代码实现:见链表的实现
1.2.5 快慢指针
- 快慢指针就是定义两个指针,这两个指针的移动速度一快一慢,以此来制造出自己想要的插值,这个差值可以帮我们找到链表上相应的结点。
- 中间值问题:找到链表的中间值
package com.tiger.study.DataStructure.Test;
import com.tiger.study.DataStructure.Linear.LinkListNode;
public class FastSlowTestMid {
public static void main(String[] args) {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
System.out.println("中间值为:" + getMid(first));
}
public static String getMid(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
while (fast.next != null && fast != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
private static class Node<T> {
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
- 单向链表是否有环问题:
package com.tiger.study.DataStructure.Test;
public class FastSlowTestLinkListCircle {
public static void main(String[] args) throws Exception {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
seven.next = third;
boolean circle = isCircle(first);
System.out.println("first链表中是否有环:"+circle);
}
public static boolean isCircle(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
while (fast != null && fast.next != null ) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)) {
return true;
}
}
return false;
}
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
package com.tiger.study.DataStructure.Test;
public class FastSlowTestLinkListCircleExit {
public static void main(String[] args) throws Exception {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
seven.next = third;
Node circle = isCircle(first);
System.out.println("first链表中环的入口:"+circle.item);
}
public static Node isCircle(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
while (fast != null && fast.next != null ) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)) {
temp = first;
continue;
}
if (temp != null) {
temp = temp.next;
if (temp.equals(slow)) {
break;
}
}
}
return temp;
}
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
1.2.6 循环链表
- 循环链表:链表的整体形成了一个圆环状。在单向链表中,最后一个结点的指针为null,不指向任何结点,因为没有下一个元素。要实现循环链表,我们只需要让单向链表的最后一个结点的指针指向头结点即可。
- 约瑟夫问题:传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。
package com.tiger.study.DataStructure.Test;
public class JosephusProblemCircleLinkList {
public static void main(String[] args) {
Node<Integer> first = null;
Node<Integer> pre = null;
for (int i = 1; i <= 41; i++) {
if (i == 1) {
first = new Node<>(i, null);
pre = first;
} else if (i != 1 && i != 41) {
pre.next = new Node<>(i, null);
pre = pre.next;
} else if (i == 41) {
pre.next = new Node<>(i, first);
}
}
int count = 0;
Node<Integer> n = first;
Node<Integer> before = null;
while (n != n.next) {
count++;
if (count == 3) {
System.out.println("当前删除的元素是:" + n.item);
n = n.next;
before.next = n;
count = 0;
} else {
before = n;
n = n.next;
}
}
System.out.println("剩余的元素是:" + n.item);
}
private static class Node<T> {
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
1.3 栈
- 概念:栈是一种基于先进后出的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后厨的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。数据进入栈称为压栈,数据从栈中出去的动作为弹栈。
- 栈的API设计
- 栈代码实现
package com.tiger.study.DataStructure.Linear;
import java.util.Iterator;
public class Stack<T> implements Iterable<T>{
private Node head;
private int N;
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
private Node n;
public SIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
private class Node {
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Stack() {
this.head = new Node(null, null);
this.N = 0;
}
public boolean isEmpty() {
return this.N == 0;
}
public int size() {
return this.N;
}
public void push(T t) {
Node newNode = new Node(t, this.head.next);
this.head.next = newNode;
this.N++;
}
public T pop() {
Node popNode = this.head.next;
if (popNode == null) {
return null;
}
this.head.next = popNode.next;
this.N--;
return popNode.item;
}
}
- 括号比配:给定一个字符串,里边可能包含"()"小括号和其他字符,编写程序查看小括号是否成对出现
package com.tiger.study.DataStructure.Test;
import com.tiger.study.DataStructure.Linear.Stack;
public class BracketsMatchTest {
public static void main(String[] args) {
String str = "(上海(长安)))";
boolean match = isMatch(str);
System.out.println(match);
}
public static boolean isMatch(String str) {
char[] chars = str.toCharArray();
Stack<Character> stack = new Stack<>();
for (char aChar : chars) {
if (aChar == '(') {
stack.push(aChar);
} else if (aChar == ')') {
if (stack.size() == 0) {
return false;
} else {
stack.pop();
}
}
}
if (stack.size() != 0) {
return false;
}
return true;
}
}
- 逆波兰表达式:求值
- 中缀表达式:二元运算符总是置于两个操作数之间。
- 逆波兰表达式(后缀表达式):运算符总是放在跟它相关的操作数之后
package com.tiger.study.DataStructure.Test;
import com.tiger.study.DataStructure.Linear.Stack;
public class ReversePolishNotationTest {
public static void main(String[] args) {
String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
System.out.println(calculate(notation));
}
public static int calculate(String[] strs) {
Stack<Integer> oprands = new Stack<>();
for (int i = 0; i < strs.length; i++) {
String curr = strs[i];
Integer o1;
Integer o2;
switch (curr) {
case "+":
o1 = oprands.pop();
o2 = oprands.pop();
oprands.push(o2 + o1);
break;
case "-":
o1 = oprands.pop();
o2 = oprands.pop();
oprands.push(o2 - o1);
break;
case "*":
o1 = oprands.pop();
o2 = oprands.pop();
oprands.push(o2 * o1);
break;
case "/":
o1 = oprands.pop();
o2 = oprands.pop();
oprands.push(o2 / o1);
break;
default:
oprands.push(Integer.parseInt(curr));
break;
}
}
return oprands.pop();
}
}
1.4 队列
- 队列是一种基于先进先出的数据结构,是一种只能在一端进入,另一端删除的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先被读出来。
- 队列的API设计
- 代码实现
package com.tiger.study.DataStructure.Linear;
import java.util.Iterator;
public class Queue<T> implements Iterable<T>{
private Node head;
private Node last;
private int N;
@Override
public Iterator<T> iterator() {
return new QIterator();
}
private class QIterator implements Iterator {
private Node n;
public QIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
private class Node {
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Queue() {
this.head = new Node(null, null);
this.last = null;
this.N = 0;
}
public boolean isEmpty() {
return this.N == 0;
}
public int size() {
return this.N;
}
public void enqueue(T t) {
Node newNode = new Node(t, null);
if (last == null) {
last = newNode;
head.next = newNode;
} else {
last.next = newNode;
last = newNode;
}
this.N++;
}
public T dequeue() {
if (isEmpty()) {
return null;
}
Node curr = head.next;
head.next = curr.next;
this.N--;
if (isEmpty()) {
last = null;
}
return curr.item;
}
}
|