一.线性表
1.1 线性表概述
1.1.1 概念
线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。
1.1.2 前驱与后驱
前驱元素:
若A元素在B元素的前面,则称A为B的前驱元素
后继元素:
若B元素在A元素的后面,则称B为A的后继元素
1.1.3 线性表的特征
数据元素之间具有一种“一对一”的逻辑关系。
- 第一个数据元素没有前驱,这个数据元素被称为头结点;
- 最后一个数据元素没有后继,这个数据元素被称为尾结点;
- 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继。
1.1.4 分类
线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。
1.1.5线性表的选择:
如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表
1.2 顺序表
1.2.1 概念
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
1.2.2 顺序表API设计
1.2.3 顺序表的代码实现和测试
package com.caopeng.ZTestpackage.Linear_table;
public class SequenceList<T> {
// 属性
// 存储元素的数组
private T[] array;
// 当前线性表的长度
private int length;
// 构造方法
// 创建一个容量为 capacity 的线性表对象
public SequenceList(int capacity) {
// 初始化数组
this.array = (T[]) new Object[capacity];
// 初始化长度
this.length = 0;
}
/**
* 清空线性表
*/
public void clear(){
this.length = 0;
}
/**
* 判断线性表是否为空
* @return
*/
public boolean isEmpty(){
return this.length == 0;
}
/**
* 返回线性表的长度
* @return
*/
public int getLength() {
return length;
}
/**
* 读取并返回线性表中第i个元素的值
* @param i
* @return
*/
public T get(int i){
// 安全性校验
if(i < 1 || i>=this.length){
throw new RuntimeException("当前元素不存在!");
}
return this.array[i];
}
/**
* 在线性表的第i个元素前插入一个值为t的元素
* @param i
* @param t
*/
public void insert(int i,T t){
// 等于数组元素的长度,而不是线性表的长度
if (i == this.array.length){
throw new RuntimeException("线性表已满");
}
if(i < 1 || i>this.length){
throw new RuntimeException("插入位置不合法!");
}
// 该数组i位置及其后面的元素都往后移一位
for (int k =i;k<this.length;k++) { // 因为线性表的数组元素还没满,所以还能往后面走
array[k+1] = array[k]; // 后一项等于前一项
}
// 把t放到第i个元素上
array[i] = t;
// 元素个数加一
this.length++;
}
/**
* 向线性表中添加一个元素t
* @param t
*/
public void insert(T t){
array[length++] = t;
}
/**
* 删除并返回线性表中的第i个元素
* @param i
* @return
*/
public T remove(int i){
if(i < 1 || i>=this.length){
throw new RuntimeException("当前元素不存在");
}
// 保存当前元素
T t = this.array[i];
// i位置之后的元素往前移动
for (int j = i; j < this.length - 1; j++) {
array[j] = array[j+1];
}
// 长度减一
this.length --;
// 返回数据
return t;
}
/**
* 返回线性表中首次出现的指定的数据元素的位置序号,若不存在,则返回-1
* @param t
* @return
*/
public int indexOf(T t){
boolean flag = false;
int i = 0;
for (; i < array.length; i++) {
if (t.equals(array[i])){
flag = true;
break;
}
}
if (flag){
return i;
}else {
return -1;
}
}
}
package com.caopeng.ZTestpackage.Linear_table;
public class SequenceListTest {
public static void main(String[] args) {
// 创建线性表对象
SequenceList<String> stringSequenceList = new SequenceList<>(10);
// 测试插入功能
System.out.println("线性表是否是空的 " + stringSequenceList.isEmpty());
stringSequenceList.insert("钢铁侠");
stringSequenceList.insert("蜘蛛侠");
stringSequenceList.insert("奇异博士");
stringSequenceList.insert(2,"雷神");
System.out.println("线性表的长度是 " + stringSequenceList.getLength());
// 测试删除功能
String s = stringSequenceList.remove(2);
System.out.println("删除了 " + s);
System.out.println("线性表的长度是 " + stringSequenceList.getLength());
System.out.println("奇异博士出现在第 " + stringSequenceList.indexOf("奇异博士") +" 个位置");
}
}
1.2.4 顺序表的遍历
提供遍历的方法:
1.实现Iterable接口,重写iterator方法.(迭代器)
2.内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法
package com.caopeng.ZTestpackage.Linear_table;
// 提供遍历的方法
// 1.实现Iterable接口,重写iterator方法.(迭代器)
// 2.内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法
import java.util.Iterator;
public class SequenceList<T> implements Iterable<T>{
// 属性
// 存储元素的数组
private T[] array;
// 当前线性表的长度
private int length;
// 构造方法
// 创建一个容量为 capacity 的线性表对象
public SequenceList(int capacity) {
// 初始化数组
this.array = (T[]) new Object[capacity];
// 初始化长度
this.length = 0;
}
/**
* 清空线性表
*/
public void clear(){
this.length = 0;
}
/**
* 判断线性表是否为空
* @return
*/
public boolean isEmpty(){
return this.length == 0;
}
/**
* 返回线性表的长度
* @return
*/
public int getLength() {
return length;
}
/**
* 读取并返回线性表中第i个元素的值
* @param i
* @return
*/
public T get(int i){
// 安全性校验
if(i < 1 || i>=this.length){
throw new RuntimeException("当前元素不存在!");
}
return this.array[i];
}
/**
* 在线性表的第i个元素前插入一个值为t的元素
* @param i
* @param t
*/
public void insert(int i,T t){
// 等于数组元素的长度,而不是线性表的长度
if (i == this.array.length){
throw new RuntimeException("线性表已满");
}
if(i < 1 || i>this.length){
throw new RuntimeException("插入位置不合法!");
}
// 该数组i位置及其后面的元素都往后移一位
for (int k =i;k<this.length;k++) { // 因为线性表的数组元素还没满,所以还能往后面走
array[k+1] = array[k]; // 后一项等于前一项
}
// 把t放到第i个元素上
array[i] = t;
// 元素个数加一
this.length++;
}
/**
* 向线性表中添加一个元素t
* @param t
*/
public void insert(T t){
array[length++] = t;
}
/**
* 删除并返回线性表中的第i个元素
* @param i
* @return
*/
public T remove(int i){
if(i < 1 || i>=this.length){
throw new RuntimeException("当前元素不存在");
}
// 保存当前元素
T t = this.array[i];
// i位置之后的元素往前移动
for (int j = i; j < this.length - 1; j++) {
array[j] = array[j+1];
}
// 长度减一
this.length --;
// 返回数据
return t;
}
/**
* 返回线性表中首次出现的指定的数据元素的位置序号,若不存在,则返回-1
* @param t
* @return
*/
public int indexOf(T t){
boolean flag = false;
int i = 0;
for (; i < array.length; i++) {
if (t.equals(array[i])){
flag = true;
break;
}
}
if (flag){
return i;
}else {
return -1;
}
}
// 重写 iterator 方法
@Override
public Iterator iterator() {
return new SIterator();
}
// 构建一个内部类实现Iterator接口,那么这个类就是 Iterator 的子类,那么就可以在iterator方法内返回
class SIterator<iterator> implements Iterator{
// 指针
private int point;
// 构造方法
public SIterator() {
point = 0;
}
// 重写basNext()和next()方法
@Override
public boolean hasNext() {
return point < length; // 指针是否小于顺序表的长度
}
@Override
public Object next() {
return array[point++]; // 返回后指针自加一
}
}
}
测试代码:
package com.caopeng.ZTestpackage.Linear_table;
public class SequenceListTest {
public static void main(String[] args) {
// 创建线性表对象
SequenceList<String> stringSequenceList = new SequenceList<>(10);
// 测试插入功能
System.out.println("线性表是否是空的 " + stringSequenceList.isEmpty());
stringSequenceList.insert("钢铁侠");
stringSequenceList.insert("蜘蛛侠");
stringSequenceList.insert("奇异博士");
stringSequenceList.insert(2,"雷神");
System.out.println("线性表的长度是 " + stringSequenceList.getLength());
// 测试删除功能
String s = stringSequenceList.remove(2);
System.out.println("删除了 " + s);
System.out.println("线性表的长度是 " + stringSequenceList.getLength());
System.out.println("奇异博士出现在第 " + stringSequenceList.indexOf("奇异博士") +" 个位置");
System.out.println("====================================");
// 测试遍历功能
for(String s1:stringSequenceList){
System.out.println(s1);
}
}
}
1.2.5 顺序表的容量可变
考虑容器的容量伸缩性,其实就是改变存储数据元素的数组的大小,我们应该对顺序的容量进行调整.
1.插入
2.删除
3.代码实现
package com.caopeng.ZTestpackage.Linear_table;
// 提供遍历的方法
// 1.实现Iterable接口,重写iterator方法.(迭代器)
// 2.内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法
import java.util.Iterator;
public class SequenceList<T> implements Iterable<T>{
// 属性
// 存储元素的数组
private T[] array;
// 当前线性表的长度
private int length;
// 构造方法
// 创建一个容量为 capacity 的线性表对象
public SequenceList(int capacity) {
// 初始化数组
this.array = (T[]) new Object[capacity];
// 初始化长度
this.length = 0;
}
/**
* 清空线性表
*/
public void clear(){
this.length = 0;
}
/**
* 判断线性表是否为空
* @return
*/
public boolean isEmpty(){
return this.length == 0;
}
/**
* 返回线性表的长度
* @return
*/
public int getLength() {
return length;
}
/**
* 读取并返回线性表中第i个元素的值
* @param i
* @return
*/
public T get(int i){
// 安全性校验
if(i < 1 || i>=this.length){
throw new RuntimeException("当前元素不存在!");
}
return this.array[i];
}
/**
* 在线性表的第i个元素前插入一个值为t的元素
* @param i
* @param t
*/
public void insert(int i,T t){
// 如果线性表的长度等于数组的长度,那么扩容两倍
if(this.array.length == this.length){
resize(this.array.length*2);
}
if(i < 1 || i>this.length){
throw new RuntimeException("插入位置不合法!");
}
// 该数组i位置及其后面的元素都往后移一位
for (int k =i;k<this.length;k++) { // 因为线性表的数组元素还没满,所以还能往后面走
array[k+1] = array[k]; // 后一项等于前一项
}
// 把t放到第i个元素上
array[i] = t;
// 元素个数加一
this.length++;
}
/**
* 向线性表中添加一个元素t
* @param t
*/
public void insert(T t){
// 如果线性表的长度等于数组的长度,那么就扩容
if(this.array.length == this.length){
resize(this.array.length*2);
}
array[length++] = t;
}
/**
* 删除并返回线性表中的第i个元素
* @param i
* @return
*/
public T remove(int i){
// 保存当前元素
T t = this.array[i];
// i位置之后的元素往前移动
for (int j = i; j < this.length - 1; j++) {
array[j] = array[j+1];
}
// 长度减一
this.length --;
// 顺序表的长度小于数组长度的4分之1,缩小容量到原来的1/2
if(this.length <= (this.array.length)/4){
resize(this.array.length/2);
}
// 返回数据
return t;
}
/**
* 返回线性表中首次出现的指定的数据元素的位置序号,若不存在,则返回-1
* @param t
* @return
*/
public int indexOf(T t){
boolean flag = false;
int i = 0;
for (; i < array.length; i++) {
if (t.equals(array[i])){
flag = true;
break;
}
}
if (flag){
return i;
}else {
return -1;
}
}
// 重写 iterator 方法
@Override
public Iterator iterator() {
return new SIterator();
}
// 构建一个内部类实现Iterator接口,那么这个类就是 Iterator 的子类,那么就可以在iterator方法内返回
class SIterator<iterator> implements Iterator{
// 指针
private int point;
// 构造方法
public SIterator() {
point = 0;
}
// 重写basNext()和next()方法
@Override
public boolean hasNext() {
return point < length; // 指针是否小于顺序表的长度
}
@Override
public Object next() {
return array[point++]; // 返回后指针自加一
}
}
/**
* 改变顺序表数组的容量大小
* @param newSize
*/
public void resize(int newSize){
// 定义一个临时数组,指向原数组,就是把原数组的信息存到一个临时数组中
T[] temp = this.array;
// 创建一个新数组
this.array = (T[]) new Object[newSize];
// 将原数组拷贝到新数组中
for(int i = 0;i <temp.length;i++){
array[i] = temp[i];
}
}
}
1.3 链表
1.3.1 概念
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成
1.3.2 节点类的实现
1.3.3 节点的API设计
1.3.4 代码实现
// 节点类
public class Node <T>{
// 存储的元素
T item;
// 下一节点
Node next;
// 构造方法
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
1.3.3 单向链表
1.3.3.1 概念
单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,
指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
1.3.3.1 API设计
1.3.3.2 代码实现
package com.caopeng.ZTestpackage.Linear_table;
import java.util.Iterator;
// 单向链表
public class SinglyLinkedList<T> implements Iterable<T>{
// 内部类,节点类
class Node{
// 下一个节点
Node next;
// 存储数据
T item;
// 构造方法
public Node(Node next, T item) {
this.next = next;
this.item = item;
}
}
// 单向链表的属性
private Node head; // 用于保存头节点,头节点不存储数据,只是用来找到这一条链表
private int length; // 链表的长度
// 构造方法
public SinglyLinkedList() {
// 初始化头节点
this.head = new Node(null,null);
// 初始化长度
this.length = 0;
}
/**
* 清空链表
*/
public void clear(){
this.length = 0;
}
/**
* 判断链表是否为空
* @return
*/
public boolean isEmpty(){
return this.length == 0;
}
/**
* 获取线性表中元素的个数
* @return
*/
public int getLength(){
return this.length;
}
/**
* 返回线性表第i个元素的值
* @param i
* @return
*/
public T get(int i){
// 第一个节点没有数据,直接从第二个节点开始
Node node = this.head.next;
// 遍历链表
for(int j = 0;j < i;j++){
node = node.next;
}
return node.item;
}
/**
* 往线性表中添加一个元素
* @param t
*/
public void insert(T t){
// 找到当前尾结点
Node node = this.head;
while (node.next != null){
node = node.next;
}
// 创建新节点
Node currentNode = new Node(null,t);
// 让最后一个节点指向新节点
node.next = currentNode;
// 长度加一
this.length++;
}
/**
* 在线性表的第i个元素之前插入一个值为t的元素
* @param i
* @param t
*/
public void insert(int i,T t){
Node node = this.head;
// 找到第i-1个元素
for(int j = 0;j<=i-1;j++){
node = node.next;
}
// 创建新的节点,让他指向第i个节点
Node newNode = new Node(node.next,t);
// 让第i-1个节点指向新节点
node.next = newNode;
// 长度增加
this.length++;
}
/**
* 删除并返回线性表中第i个数据元素
* @return
* @param i
*/
public T remove(int i){
Node node = this.head;
// 找到第i-1个节点
for(int j = 0;j<=i-1;j++){
node = node.next;
}
// 保存元素
Node currentNode = node.next;
// 让第i-1个节点指向第i+1个节点
node.next = node.next.next;
// 长度缩短
this.length--;
// 返回元素数据
return currentNode.item;
}
/**
* 返回线性表中首次出现的指定数据元素的位序号,若不存在,则返回-1
* @param t
* @return
*/
public int indexOf(T t){
Node node = this.head;
// 遍历链表
for(int i = 0;node.next!=null;i++){
node = node.next;
if(t.equals(node.item)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new Singleiterator();
}
class Singleiterator implements Iterator<T> {
private Node node;
public Singleiterator() {
this.node = head;
}
@Override
public boolean hasNext() {
return node.next != null;
}
@Override
public T next() {
node = node.next;
return node.item;
}
}
}
1.3.3.3 测试代码
class SinglyLinkedListTest {
public static void main(String[] args) {
// 创建链表
SinglyLinkedList<String> stringSinglyLinkedList = new SinglyLinkedList<>();
// 测试增删
stringSinglyLinkedList.insert("钢铁侠");
stringSinglyLinkedList.insert("美国队长");
stringSinglyLinkedList.insert("蜘蛛侠");
stringSinglyLinkedList.insert(1,"黑豹");
System.out.println("链表的长度为 " + stringSinglyLinkedList.getLength());
String s = stringSinglyLinkedList.remove(2);
System.out.println("删除的元素是 " + s);
System.out.println("黑豹在 " + stringSinglyLinkedList.indexOf("黑豹"));
//测试遍历
for(String s1 : stringSinglyLinkedList){
System.out.println(s1);
}
}
}
1.3.4 双向链表
1.3.4.1 API设计
1.3.4.1 代码实现
package com.caopeng.ZTestpackage.Linear_table;
import java.util.Iterator;
public class DoublyLinkedList<T> implements Iterable<T>{
// 节点类
private class Node{
// 属性
T item; // 存储的数据
Node pre; // 前一个节点
Node next; // 后一个节点
// 构造方法
public Node(Node pre, Node next, T item) {
this.next = next;
this.pre = pre;
this.item = item;
}
}
// 属性
private Node first; // 记录头节点
private Node last; // 记录尾结点
private int length;
// 构造方法,初始化双向链表
public DoublyLinkedList() {
this.first = new Node(null,null,null);
this.last = null;
this.length = 0;
}
/**
* 清空链表
*/
public void clear(){
this.first.next = null;
this.last = null;
this.length = 0;
}
/**
* 判断链表是否为空
* @return
*/
public boolean isEmpty(){
return this.length == 0;
}
/**
* 返回线性表中元素个数
* @return
*/
public int getLength(){
return this.length;
}
/**
* 返回线性表中第i个元素的值
* @return
*/
public T get(int i){
Node node = first.next;
for (int j = 0; j < i; j++) {
node = node.next;
}
return node.item;
}
/**
* 往线性表中添加一个元素
* @param t
*/
public void insert(T t){
// 如果是空的
if(isEmpty()){
// 创建一个新节点
Node node = new Node(first,null,t);
// 让新节点成为尾结点
last = node;
// 让头节点指向尾节点
first.next = last;
}else{
Node oldLast = last;
// 创建新节点
Node newNode = new Node(oldLast, null, t);
// 让原来的尾节点指向新的尾结点
oldLast.next = newNode;
// 让当前尾结点成为尾结点
last = newNode;
}
this.length++;
}
/**
* 在线性表的第i个元素前添加一个元素
* @param i
* @param t
*/
public void insert(int i,T t){
Node pre = first;
// 找到第i-1个节点
for (int j = 0; j <= i-1; j++) {
pre = pre.next;
}
// 第i个节点
Node currentNode = pre.next;
// 创建新节点
Node newNode = new Node(pre, currentNode, t);
// 让原来的第i个节点前驱指向新节点
currentNode.pre = newNode;
// 第i-1个节点指向新节点
pre.next = newNode;
// 长度加一
this.length++;
}
/**
* 删除并返回线性表中的第i个元素
* @param i
* @return
*/
public T remove(int i){
// 找到第i-1个线性表
Node perNode = first;
for (int j = 0; j <= i-1; j++) {
perNode = perNode.next;
}
// 找到i位置的节点
Node currentNode = perNode.next;
// 找到i位置节点后一个节点
Node nextNode = currentNode.next;
// 让i位置前一个节点的后一个节点变成i位置后一个节点
perNode.next = nextNode;
// 让i位置后一个节点的前一个节点变成i位置前一个节点
nextNode.pre = perNode;
// 链表长度减一
this.length --;
// 返回被删除的元素
return currentNode.item;
}
/**
* 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
* @param t
* @return
*/
public int indexOf(T t){
Node node = first;
for (int i = 0;node.next!=null;i++){
if(t.equals(node.item)){
return i;
}else{
node = node.next;
}
}
return -1;
}
/**
* 返回第一个元素
* @return
*/
public T getFirst(){
if(isEmpty()){
return null;
}else {
return first.next.item;
}
}
/**
* 返回最后一个元素
* @return
*/
public T getLast(){
if(isEmpty()){
return null;
}else{
return last.item;
}
}
@Override
public Iterator<T> iterator() {
return new NewDoubleLinkedListIterator();
}
private class NewDoubleLinkedListIterator implements Iterator<T> {
private Node node;
public NewDoubleLinkedListIterator() {
this.node = first;
}
@Override
public boolean hasNext() {
return node.next!=null;
}
@Override
public T next() {
node = node.next;
return node.item;
}
}
}
1.3.4.2 测试代码
class DoublyLinkedListTest {
public static void main(String[] args) {
// 创建双向链表
DoublyLinkedList<String> stringDoublyLinkedList = new DoublyLinkedList<>();
// 测试增删
stringDoublyLinkedList.insert("钢铁侠");
stringDoublyLinkedList.insert("美国队长");
stringDoublyLinkedList.insert("蜘蛛侠");
stringDoublyLinkedList.insert(1,"黑豹");
System.out.println("双线链表元素个数: " + stringDoublyLinkedList.getLength());
String str = stringDoublyLinkedList.remove(2);
System.out.println("删除的元素是 :" + str);
// 测试遍历
for(String s:stringDoublyLinkedList){
System.out.println(s);
}
}
}
1.3.5 链表的反转
1.3.5.1 API设计
1.3.5.2 代码实现
/**
* 对整个链表进行反转
*/
public void reverse(){
// 如果是空链表,直接结束
if(isEmpty()){
return;
}
reverse(head.next);
}
/**
* 对指定的节点进行反转
* @param current
*/
private Node reverse(Node current) {
// 递归的出口,到了尾部,那么让头节点指向当前节点
if (current.next == null){
head.next = current;
return current;
}
// 让当前节点的下一节点进行反转,返回值就是反转后,当前节点的上一个节点
Node pre = reverse(current.next);
// 让返回的节点的下一个节点变为当前节点
pre.next = current;
// 把当前节点的下一节点设为null
current.next = null;
return current;
}
1.3.5.3 测试代码
//测试遍历
for(String s1 : stringSinglyLinkedList){
System.out.println(s1);
}
System.out.println("================================");
// 测试反转
stringSinglyLinkedList.reverse();
for(String s1 : stringSinglyLinkedList){
System.out.println(s1);
}
1.3.6 快慢指针
快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍
1.3.6.1 中间值问题
利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的
代码实现:
// 设计一个可以得到中间值的方法
private static String getMid(Node<String> first) {
// 定义两个指针
Node<String> fast = first;
Node<String> slow = first;
// 遍历链表
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
测试代码
package com.caopeng.ZTestpackage.Linear_table;
public class FastSlowPointTest01 {
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;
//查找中间值
String mid = getMid(first);
System.out.println("中间值为:"+mid);
}
// 设计一个可以得到中间值的方法
private static String getMid(Node<String> first) {
// 定义两个指针
Node<String> fast = first;
Node<String> slow = first;
// 遍历链表
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
// 节点类
private static class Node<T>{
// 存储的数据
T item;
// 下一节点
Node next;
// 构造方法
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
1.3.6.2 单向链表是否有环问题
使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇,只要相遇那么就说明有环。
代码实现
/**
* 判断节点是否有环
* @param node
* @return
*/
public static boolean isCircle(Node node) {
Node fast = node;
Node slow = node;
while(fast.next != null && slow.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast.equals(slow)){
return true;
}
}
return false;
}
测试代码
package com.caopeng.ZTestpackage.Linear_table;
public class FastSlowPointTest02 {
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);
}
/**
* 判断节点是否有环
* @param node
* @return
*/
public static boolean isCircle(Node node) {
Node fast = node;
Node slow = node;
while(fast.next != null && slow.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;
}
}
}
1.3.6.3 有环链表入口问题
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识,这里略,只讲实现
代码实现
/**
* 查找有环链表中的环入口节点
* @param first 链表的头节点
* @return 环入口节点
*/
public static Node getEntrance(Node<String> first) {
// 定义三个指针
Node fast = first;
Node slow = first;
Node temp = null;
// 先让快指针和慢指针相遇
while(fast.next!=null && slow.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast.equals(slow)){
temp = first;
continue;
}
// 让临时指针移动
if(temp != null){
temp = temp.next;
// 判断慢指针和临时指针是否相遇
if(slow.equals(temp)){
break;
}
}
}
return temp;
}
测试代码
package com.caopeng.ZTestpackage.Linear_table;
public class FastSlowPointTest03 {
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<String> entrance = getEntrance(first);
System.out.println("first链表中环的入口结点元素为:"+entrance.item);
}
/**
* 查找有环链表中的环入口节点
* @param first 链表的头节点
* @return 环入口节点
*/
public static Node getEntrance(Node<String> first) {
// 定义三个指针
Node fast = first;
Node slow = first;
Node temp = null;
// 先让快指针和慢指针相遇
while(fast.next!=null && slow.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast.equals(slow)){
temp = first;
continue;
}
// 让临时指针移动
if(temp != null){
temp = temp.next;
// 判断慢指针和临时指针是否相遇
if(slow.equals(temp)){
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.3.7 循环链表
1.3.7.1 概念
1.3.7.2 约瑟夫问题
问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。
问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。
代码实现
package com.caopeng.ZTestpackage.Linear_table;
/*
问题描述:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。
*/
public class JosephTest {
public static void main(String[] args) {
// 解决约瑟夫问题
// 1.通过循环定义出41个节点的循环链表
// 头节点
Node head = null;
// 用来记录前一个节点
Node pre = null;
for (int i = 1; i <= 41; i++) {
if(i==1){
head = new Node(i,null);
pre = head;
continue;
}
// 创建一个新节点
Node newNode = new Node(i,null);
// 上一个节点指向新节点
pre.next =newNode;
// 本次循环结束,当前节点就变成了前一个节点了
pre = newNode;
if (i==41){
pre.next = head;
}
}
// 开始模拟报数
int count = 0;
// 开始遍历链表
Node n = head; // 从头开始
Node before = null; // 记录前一个节点
while(n.next!=n){
count++;
// 如果是三就要死一个人
if(count == 3){
// 把当前节点删除
System.out.println("本轮淘汰 " + n.item);
before.next = n.next; // 前一个节点指向下一个节点
count = 0;
// 往后走
n = n.next;
}else{
// 不是的话就往后走,让前一个节点变成当前节点
before = n;
n = n.next;
}
}
// 打印最后一个元素
System.out.println("最后一个元素是 " + n.item);
}
// 节点类
private static class Node<T>{
// 属性
Node next;
T item;
// 构造方法
public Node(T item,Node next) {
this.next = next;
this.item = item;
}
}
}
1.4 栈
1.4.1 栈的定义
栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
1.4.2 栈的API设计
1.4.3 栈的代码实现
package com.caopeng.Linear_table;
public class StackTest {
public static void main(String[] args) {
// 创建栈对象
Stack<String> stringStack = new Stack<>();
// 测试压栈
stringStack.push("a");
stringStack.push("b");
stringStack.push("c");
stringStack.push("d");
// 遍历输出
for(String s:stringStack){
System.out.println(s);
}
// 测试弹栈
String result = stringStack.pop();
System.out.println("弹栈的元素是 " + result);
System.out.println("剩余的元素个数是 " + stringStack.size());
}
}
测试代码
public class StackTest {
public static void main(String[] args) {
// 创建栈对象
Stack<String> stringStack = new Stack<>();
// 测试压栈
stringStack.push("a");
stringStack.push("b");
stringStack.push("c");
stringStack.push("d");
// 遍历输出
for(String s:stringStack){
System.out.println(s);
}
// 测试弹栈
String result = stringStack.pop();
System.out.println("弹栈的元素是 " + result);
System.out.println("剩余的元素个数是 " + stringStack.size());
}
}
1.4.4 括号匹配问题
1.4.5 逆波兰表达式
1.5 队列
1.5.1 队列的定义
队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来
1.5.2 队列的API设计
1.5.3 队列的代码实现
package com.caopeng.ZTestpackage.Linear_table;
import java.util.Iterator;
// 队列
public class Queue<T> implements Iterable<T>{
// 记录首节点
private Node head;
// 记录最后一个节点
private Node last;
// 记录队列中元素的个数
private int length;
// 节点内部类
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.length = 0;
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty(){
return this.length == 0;
}
/**
* 返回队列的长度
* @return
*/
public int getLength(){
return this.length;
}
/**
* 向队列中插入数据
* @param t
*/
public void enqueue(T t){
// 当前尾结点last为null
if (last == null){
last = new Node(t,null);
head.next = last;
}else{
// 当前尾结点last不为null
Node oldLast = last; // 用 oldLast 指向原来的尾结点
last = new Node(t, null); // last就是尾结点,此时last是新插进来的元素
oldLast.next=last; // oldLast 是原来的尾结点 现在原来的尾结点指向 新的尾结点
}
// 元素个数加一
this.length ++;
}
/**
* 从队列中取出一个元素
* @return
*/
public T dequeue(){
if (isEmpty()){
return null;
}
// 不为空 就弹出head指向的那个节点
Node oldFirst = head.next;
head.next = oldFirst.next;
this.length--;
// 如果删除完为空,那就重置队列
if (isEmpty()){
last = null;
}
return oldFirst.item;
}
@Override
public Iterator<T> iterator() {
return new QueueIterator();
}
class QueueIterator implements Iterator{
private Node node;
public QueueIterator() {
this.node = head;
}
@Override
public boolean hasNext() {
return node.next != null;
}
@Override
public Object next() {
node = node.next;
return node.item;
}
}
}
测试代码
package com.caopeng.ZTestpackage.Linear_table;
public class QueueTest {
public static void main(String[] args) {
// 创建队列对象
Queue<String> queue = new Queue<>();
//测试队列的enqueue方法
queue.enqueue("钢铁侠");
queue.enqueue("蜘蛛侠");
queue.enqueue("雷神");
queue.enqueue("美国队长");
for (String s:queue){
System.out.println(s);
}
System.out.println("------------");
//测试队列的dequeue方法
String result = queue.dequeue();
System.out.println("出队列的元素是 " + result);
System.out.println("队列剩余元素的个数" + queue.getLength());
}
}