# Data structure and algorithm series notes 3: linear table

Keywords: Algorithm data structure linked list

### Linear table

Linear table: a linear table is a finite sequence of n data elements with the same characteristics.

Related concepts:

• Precursor element, successor element
• Head node and tail node

Linear list classification: sequential list and linked list

#### 1. Sequence table

Sequence table: sequence table is a linear table saved in the form of array in computer memory.

• Example: ArrayList
##### 1.1 implementation of sequence table
```package com.example.algorithm.linear;

public class SequenceList<T> {
//An array of storage elements
private T[] eles;
//Record the number of elements in the current sequence table
private int N;

public SequenceList(int capacity) {
//Initialize array
this.eles = (T[]) new Object[capacity];
//Initialization length
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) {
eles[N++] = t;
}

public void insert(int i, T t) {
//First, move the i index and its subsequent elements backward one bit in turn
for (int index = N; index > i; index--) {
eles[index] = eles[index - 1];
}
//Then put the t element at the i index
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--;
return current;
}

public int indexOf(T t) {
for (int i = 0; i < N; i++) {
if (eles[i].equals(t)) {
return i;
}
}
return -1;
}
}

```
##### 1.2 traversal of sequence table

1. Let SequenceList implement Iterable interface and rewrite iterator method;
2. Provide an internal class SIterator inside the SequenceList, implement the Iterator interface, and rewrite hasNext method and next method;

```@Override
public Iterator<T> iterator() {
return new SIterator();
}

private class SIterator implements Iterator {
private int cursor;

public SIterator() {
this.cursor = 0;
}

@Override
public boolean hasNext() {
return cursor < N;
}

@Override
public Object next() {
return eles[cursor++];
}
}
```
##### 1.3 variable capacity of sequence table
```  //Variable capacity method
private void resize(int newSize) {
T[] temp = eles;
eles = (T[]) new Object[newSize];
for (int i = 0; i < N; i++) {
eles[i] = temp[i];
}
}

public void insert(T t) {
if (N == eles.length) {
resize(2 * eles.length);
}
eles[N++] = t;
}

public void insert(int i, T t) {
if (N == eles.length) {
resize(2 * eles.length);
}
//First, move the i index and its subsequent elements backward one bit in turn
for (int index = N; index > i; i--) {
eles[index] = eles[index - 1];
}
//Then put the t element at the i index
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 < eles.length / 4) {
resize(eles.length / 2);
}
return current;
}
```
##### 1.4 time complexity of sequence table
• get(i): it is not difficult to see that no matter how large the number of data elements N is, the corresponding elements can be obtained only once eles[i], so the time complexity is O(1);
• Insert (int i, t, t): every time you insert, you need to move the elements behind I. with the increase of the number of elements N, the more elements are moved, and the time complexity is O(n);
• remove(int i): every time you delete, you need to move the elements behind the I position once. With the increase of the amount of data N, more elements are moved, and the time complexity is O(n);
• Because the bottom layer of the sequence table is implemented by an array, and the length of the array is fixed, the container expansion operation is involved in the process of operation. In this way, the time complexity of the sequence table in the use process is not linear. At some nodes that need to be expanded, the time will increase sharply, especially the more elements, the more obvious the problem is
##### 1.5 ArrayLsit implementation

The bottom layer of the ArrayLsit set is the sequence table, which is implemented by array.

Although the query of sequential table is fast and the time complexity is O(1), the efficiency of addition and deletion is relatively low, because each addition and deletion operation is accompanied by a large number of data elements. Is there a solution to this problem? Yes, we can use another storage structure to realize linear list and chain storage structure.
Linked list: it is a discontinuous and non sequential storage structure on the physical storage unit. Its physical structure cannot only represent the logical order of data elements. The logical order of data elements is realized through the pointer link order in the linked list. The linked list consists of a series of nodes (each element in the linked list is called a node), which can be generated dynamically at run time.

Node.java

```public class Node<T> {
//Storage element
public T item;
//Point to the next node
public Node next;

public Node(T item, Node next) {
this.item = item;
this.next = next;
}

}
```

```public static void main(String[] args) {
//Build node
Node<Integer> first = new Node<>(11, null);
Node<Integer> second = new Node<>(5, null);
Node<Integer> third = new Node<>(8, null);
Node<Integer> fourth = new Node<>(2, null);
Node<Integer> fifth = new Node<>(74, null);
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
}
```
##### 2.1 one way linked list

Data field + pointer field ```package com.example.algorithm.linear;

//Record the length of the linked list
private int length;

//Node class
private class Node {
//Store data
T item;
//Next node
Node next;

public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}

//Number of initialization elements
this.length = 0;
}

public void clear() {
this.length = 0;
}

public int size() {
return length;
}

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

public T get(int i) {
//Through the loop, start from the node and look back, i times in turn, you can find the corresponding element
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}

public void insert(T t) {
//Find the last current node
while (n.next != null) {
n = n.next;
}
//Create a new node and save the element t
Node newNode = new Node(t, null);
//Make the current last node point to the new node
n.next = newNode;
//Number of elements + 1
length++;
}

public void insert(T t, int i) {
//Find the previous node at position i
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//Find i location node
Node curr = pre.next;
//Create a new node, and the new node needs to point to the node at the original i location
Node newNode = new Node(t, curr);
//The previous node in the original i position can point to the new node
pre.next = newNode;
//Number of elements + 1
length++;
}

public T remove(int i) {
//Find the previous node at position i
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//Find the node at position i
Node curr = pre.next;
//Find the next node at position i
Node nextNode = curr.next;
//The previous node points to the next node
pre.next = nextNode;
//Number of elements - 1
length--;
return curr.item;
}

public int indexOf(T t) {
for (int i = 0; n.next != null; i++) {
n = n.next;
if (n.item.equals(t)) {
return i;
}
}
return -1;
}
}

```

```  @Override
public Iterator<T> iterator() {
return new Itr();
}

private class Itr implements Iterator {
private Node n;

public Itr() {
}

@Override
public boolean hasNext() {
return n.next != null;
}

@Override
public Object next() {
n = n.next;
return n.item;
}
}
```

One data field + two pointer fields ```package com.example.algorithm.linear;

import java.util.Iterator;

public class TwoWayLinkList<T> implements Iterable<T> {
//First node
//Last node
private Node last;
private int length;

@Override
public Iterator iterator() {
return new Itr();
}

private class Itr implements Iterator<T> {
private Node n;

public Itr() {
}

@Override
public boolean hasNext() {
return n.next != null;
}

@Override
public T next() {
n = n.next;
return n.item;
}
}

//Node class
private class Node {
//Store data
public T item;
//Point to previous node
public Node pre;
//Point to the next node
public Node next;

public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
}

//Initialize head node and tail node
this.head = new Node(null, null, null);
this.last = null;
//Number of initialization elements
this.length = 0;
}

public void clear() {
this.last = null;
this.length = 0;
}

public int size() {
return length;
}

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

public T getFirst() {
return isEmpty() ? null : head.next.item;
}

public T getLast() {
return isEmpty() ? null : last.item;
}

public void insert(T t) {
if (isEmpty()) {
last = new Node(t, head, null);
} else {
Node oldLast = last;
Node newNode = new Node(t, oldLast, null);
oldLast.next = newNode;
last = newNode;
}
length++;
}

public void insert(T t, int i) {
for (int index = 0; index < i; index++) {
pre = pre.next;
}
Node next = pre.next;
Node newNode = new Node(t, pre, next);
pre.next = newNode;
next.pre = newNode;
length++;
}

public T get(int i) {
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}

public int indexOf(T t) {
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) {
for (int index = 0; index < i; index++) {
pre = pre.next;
}
Node curr = pre.next;
Node nextNode = curr.next;
pre.next = nextNode;
nextNode.pre = pre;
length--;
return curr.item;
}
}

```
##### 2.3 application scenario of linked list

1. Recursive inversion

The recursive inversion method inverts the subsequent nodes before inverting the current node. In this way, we start from the beginning node and go deep layer by layer until the end node begins to reverse the direction of the pointer field. In short, it starts from the tail node,

```public static Node reverse(Node head) {
}
return pre;
}
```

2. Ergodic inversion

The recursive inversion method reverses the direction of the pointer field from back to front, while the traversal inversion method reverses the direction of the pointer field of each node from front to back.

The basic idea is: cache the next node cur.getNext() of the current node cur to temp, and then change the current node pointer to the previous node pre. In other words, before reversing the current node pointer, the pointer field of the current node is temporarily saved with tmp for next use

```public static Node reverse(Node head) {
}
Node pre = null;
Node next = null;
}
return pre;
}
```

2. Speed pointer

Speed pointer refers to defining two pointers, and the movement speed of the two pointers is slow one by one, so as to create the desired difference. This difference can enable us to find the corresponding node on the linked list. In general, the moving step of the fast pointer is twice that of the slow pointer

2.1 intermediate value problem

As shown in the following figure, at first, the slow and fast pointers point to the first node of the linked list, then slow moves one pointer at a time, and fast moves two pointers at a time. ```/**
* @param first The first node of the linked list
* @return The value of the intermediate node of the linked list
*/
public static String getMid(Node<String> first) {
//Define two pointers
Node<String> fast = first;
Node<String> slow = first;
//Use two pointers to traverse the linked list. When the node pointed to by the fast pointer has no next node, it ends; At the end, the node pointed to by the slow pointer is the intermediate value
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}

return slow.item;
}
```

2.2 is there a ring problem in the one-way linked list ```  /**
*
* @param first First node of linked list
* @return ture Is ring, false is acyclic
*/
public static boolean isCircle(Node<String> first) {
//Define speed pointer
Node<String> fast = first;
Node<String> slow = first;
//Traverse the linked list. If the speed pointer points to the first node, it proves that there is a ring
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)) {
return true;
}
}
return false;
}
```

2.3 problem of linked list entry

When the fast and slow pointers meet, we can judge that there is a ring in the linked list. At this time, reset a new pointer to the starting point of the linked list, and the step size is 1 as the slow pointer. Then the place where the slow pointer meets the "new" pointer is the entrance of the ring. Proving this conclusion involves the knowledge of number theory, which is omitted here and only about realization. ```  /**
* Find the entry node of a ring in a linked list
*
* @param first First node of linked list
* @return Entrance node of ring
*/
public static Node getEntrance(Node<String> first) {
//Define speed pointer
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
//Traverse the linked list and find the ring first (the fast and slow pointers meet). Prepare a temporary pointer to the first node of the linked list and continue to traverse until the slow pointer meets the temporary pointer. Then the node pointed to when meeting is the entrance of the ring.
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
//Judge whether the speed pointer meets
if (fast.equals(slow)) {
temp = first;
break;
}
}
while (temp != null) {
temp = temp.next;
slow = slow.next;
if (temp.equals(slow)) {
break;
}
}
return temp;
}
```

Circular linked list, as the name suggests, the whole linked list should form a circular ring. In a one-way linked list, the pointer of the last node is null and does not point to any node, because there is no next element. To realize the circular linked list, we only need to make the pointer of the last node of the one-way linked list point to the head node. 4. Joseph problem

Circular linked list to solve Joseph problem

Problem Description:
It is said that after the Romans occupied jotapat, 39 Jews hid in a cave with Joseph and his friends. 39 Jews decided to die rather than be caught by the enemy, so they decided to commit suicide. 41 people lined up in a circle. The first person counted off from 1, and then, if someone counted off to 3, Then this person must commit suicide, and then his next person starts counting again from 1 until everyone commits suicide. However, Joseph and his friends did not want to comply. So Joseph asked his friend to pretend to obey first. He arranged his friend and himself in the 16th and 31st positions, so as to escape the death game.

Problem conversion:
41 people sit in a circle. The first person is numbered 1, the second person is numbered 2, and the nth person is numbered n.

1. The person with number 1 starts to count back from 1, and the person with number 3 quits the circle;
2. The next person starting from the person who quit will count off from 1 again, and so on;
3. Find the number of the person who quit last.

```package study.algorithm.sort;

public class Joseph {

public static void main(String[] args) {
JosephMethod(41, 3);

}

public static void JosephMethod(int totalNum, int countNum) {
//1. Building a one-way circular linked list
//First is used to record the first node, and pre records the previous node
Node<Integer> pre = null;
Node<Integer> first = null;
for (int i = 1; i <= totalNum; i++) {
if (i == 1) {
first = new Node<>(i, null);
pre = first;
continue;
}
//If not the first node
Node newNode = new Node(i, null);
pre.next = newNode;
pre = newNode;
//If it is the last node, you need to make the next node of the last node become first and a circular linked list
if (i == totalNum) {
pre.next = first;
}
}
//2. count counter is required to simulate the number of alarms
int count = 0;
//Record the nodes obtained in each traversal, starting from the first node by default
Node<Integer> n = first;
//Record the previous node of the current node
Node<Integer> before = null;
while (n != n.next) {
//Analog alarm
count++;
if (count == totalNum) {
before.next = n.next;
System.out.print(n.item + ",");
count = 0;
} else {
before = n;
}
n = n.next;
}
//Print last element
System.out.print(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;
}
}
}

```

#### 3. Stack

Linear tables have two special data structures:

Stack

• Sequential stack
• Two stack shared space
• Chain stack

queue

• Sequential queue
• Circular queue
• Chain queue

Stack: first in, then out. A special linear table that can only be inserted and deleted at one end.

Stack can be realized by chain data structure or sequential data structure

##### Implementation of stack ```package com.example.algorithm.linear;
import java.util.Iterator;

public class Stack<T> implements Iterable<T> {
//Record first node
//Number of elements in the stack
private int N;

@Override
public Iterator<T> iterator() {
return new Itr();
}

private class Itr implements Iterator<T> {
private Node n;

public Itr() {
}

@Override
public boolean hasNext() {
return n.next != null;
}

@Override
public T 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.N = 0;
}

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

public int size() {
return N;
}

//Stack pressing
public void push(T t) {
//Find the first node pointed to by the first node
//Create a new node
Node newNode = new Node(t, null);
//Let the first node point to the new node
//Let the new node point to the original first node
newNode.next = oldFirst;
//Number of elements + 1
N++;
}

//Bomb stack
public T pop() {
//Find the first node pointed to by the first node
//Let the first node point to the next node of the original first node
if (oldFirst == null) {
return null;
}
//Number of elements - 1
N--;
return oldFirst.item;
}

}
```

Array implementation stack

```package com.example.algorithm.linear;

import java.util.Iterator;

public class Stack2<T> implements Iterable<T> {
private T[] items;
private int N;

public Stack2(int capacity) {
this.items = (T[]) new Object[capacity];
this.N = 0;
}

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

public int size() {
return N;
}

public void push(T t) {
if (N == items.length) {
resize(2 * items.length);
}
items[N++] = t;
}

public T pop() {
T pop = items[--N];
items[N] = null;
if (N < items.length / 4) {
resize(items.length / 2);
}
return pop;
}

private void resize(int capacity) {
T[] temp = items;
items = (T[]) new Object[capacity];
for (int i = 0; i < N; i++) {
items[i] = temp[i];
}
}

@Override
public Iterator<T> iterator() {
return new Itr();
}

private class Itr implements Iterator<T> {
private int cursor;

public Itr() {
this.cursor = 0;
}

@Override
public boolean hasNext() {
return cursor < N;
}

@Override
public T next() {
return items[cursor++];
}
}
}
```
##### Application of stack

1. Bracket matching problem

Problem Description:

```Given a string, it may contain"()"Parentheses and other characters, write a program to check whether the parentheses in the string appear in pairs.
For example:
"(Shanghai)(Chang'an)": Correct match
"Shanghai((Chang'an))": Correct match
"Shanghai(Chang'an(Beijing)(Shenzhen)Nanjing)":Correct match
"Shanghai(Chang'an))": Wrong match
"((Shanghai)Chang'an": Wrong match
```
```  /**
* Determine whether the parentheses in str match
*
* @param str A string of parentheses
* @return Returns true if there is a match, and false if there is no match
*/
public static boolean isMatch(String str) {
String[] strings = str.split("");
Stack<String> stack = new Stack<>();
for (String string : strings) {
if ("(".equals(string)) {
stack.push(str);
} else if (")".equals(string)) {
String pop = stack.pop();
if (pop == null) {
return false;
}
}
}
return stack.size() == 0;
}
```

2. Inverse Polish expression evaluation problem

Infix expression
Infix expressions are expressions used in our daily life, such as 1 + 3 * 2,2 - (1 + 3), etc. the characteristic of infix expressions is that binary operators are always placed between two operands.
Inverse Polish expression (suffix expression)
The characteristic of suffix expression: the operator is always placed after the operand associated with it. demand
Given an array representation of an inverse Polish expression containing only four operations of addition, subtraction, multiplication and division, the result of the inverse Polish expression is obtained.

```package com.example.algorithm.linear;

public class ReversePolishNotation {
public static void main(String[] args) {
//The inverse Polish expression of infix expression 3 * (17-15) + 18 / 6 is as follows
String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
int result = calculate(notation);
System.out.println("The result of the inverse Polish expression is:" + result);
}

/**
* @param notaion Array representation of inverse Polish expressions
* @return Calculation results of inverse Polish expression
*/
public static int calculate(String[] notaion) {
Stack<Integer> operators = new Stack<>();
for (String s : notaion) {
Integer o1 = 0;
Integer o2 = 0;
switch (s) {
case "+":
o1 = operators.pop();
o2 = operators.pop();
operators.push(o2 + o1);
break;
case "-":
o1 = operators.pop();
o2 = operators.pop();
operators.push(o2 - o1);
break;
case "*":
o1 = operators.pop();
o2 = operators.pop();
operators.push(o2 * o1);
break;
case "/":
o1 = operators.pop();
o2 = operators.pop();
operators.push(o2 / o1);
break;
default:
operators.push(Integer.parseInt(s));
break;
}
}
return operators.pop();
}
}

```
##### Prefix, suffix, infix expression

Example:

```Infix expression: a+b*c+(d*e+f)*g
Prefix expression:++a*bc*+*defg
Suffix expression: abc*+de*f+g*+
```

Infix expressions are convenient for people to understand and calculate, but prefix and suffix expressions are more convenient for computer operation

There are three ways to convert infix to prefix and suffix:

1. Stack based algorithm (relatively complex, for computer implementation)

2. Bracket method

3. Semantic tree

Bracket method:

1. Parenthesize all arithmetic units according to the priority of the operator

```((a+(b*c))+(((d*e)+f)*g))
```
2. Convert to prefix and suffix expressions

• Prefix: move the operation symbol to the front of the corresponding bracket
• Suffix: move the operation symbol after the corresponding bracket

Semantic tree • Infix: first left, then middle, then right
• Prefix: first middle, then left, then right
• Suffix: first left, then right, in

Two stack shared space

For the array implementation stack, if it is two stacks with the same data type, the method of using both ends of the array as the bottom of the stack can be used to make the two stacks share data, so as to maximize the space of the array.

• The shared space of two stacks is usually when the space requirements of two stacks are opposite, that is, one stack is growing and the other stack is shortening. This makes sense to use two stacks of shared space.
```package com.example.algorithm.linear;

import org.omg.CORBA.Object;

public class TwoStack<T> {
private T[] items;
private int n1;
private int n2;

public TwoStack(int capacity) {
this.items = (T[]) new Object[capacity];
this.n1 = 0;
this.n2 = capacity - 1;
}

public void push(T t, int stackNum) {
if (stackNum != 1 && stackNum != 2 || n1 == n2) {
return;
}
if (stackNum == 1) {
items[n1++] = t;
} else {
items[n2--] = t;
}
}

public T pop(int stackNum) {
if (stackNum != 1 && stackNum != 2) {
return null;
}
if (stackNum == 1) {
if (n1 == 0) {
return null;
} else {
return items[--n1];
}
} else {
if (n2 == items.length - 1) {
return null;
} else {
return items[++n2];
}
}
}

}
```

#### 4. Queue

Queue: first in first out. It is a special linear table that can only be inserted at one end and deleted at the other end.

That is, it can be realized by sequential data structure or chain data structure ##### Implementation of queue
```package com.example.algorithm.linear;

import java.util.Iterator;

public class Queue<T> implements Iterable<T> {
//Record first node
//Record the last node
private Node last;
//Record the number of elements in the queue
private int N;

@Override
public Iterator<T> iterator() {
return new Itr();
}

private class Itr implements Iterator<T> {
private Node n;

public Itr() {
}

@Override
public boolean hasNext() {
return n.next != null;
}

@Override
public T 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.last = null;
this.N = 0;
}

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

public int size() {
return N;
}

//Insert element t into queue
public void enqueue(T t) {
if (last == null) {
last = new Node(t, null);
} else {
Node oldLast = last;
last = new Node(t, null);
oldLast.next = last;
}
N++;
}

//Take an element from the queue
public T dequeue() {
if (isEmpty()) {
return null;
}
N--;
return oldFirst.item;
}
}

```
##### Circular queue related problems (C language, array implementation)
• Front points to the queue head element, and rear points to the next position of the queue tail element (in Java, that is, front=0,rear=arr.length)
• When the queue is empty, front=rear=0

Circular queue

When the queue "false overflow", it will start from the beginning and end to end. That is, in case of false overflow, rear=1

At this point, the problem comes: when the queue is empty, front=rear, then when the queue is full, front=rear. How to judge whether the team is full or empty?

1. Method 1: set a variable flag. When front=rear and flag=0, the queue is empty; When front=rear, flag=1, the queue is full
2. Method 2: modify the team full condition and reserve an element space. That is, when the queue is full, there is another free cell in the array. That is, front+rear+1==queueSize
• (rear+1)%queueSize==front
• General queue calculation formula: (rear front + queuesize)% queuesize

Posted by upit on Mon, 20 Sep 2021 04:11:38 -0700