java 线性表
线性表
java很多包装类已经实现了很多数据结构,为什么我们还要学会自己来实现呢?
- 因为 java 给封装好的数据结构为了保证代码的通用性,所以比较臃肿 。
- 在实际的开发需求中,封装的数据结构有可能不能满足我们特殊场景的要求。
- 这时候,如果我们能自己实现这些数据结构,那么既可以提升效率,也可以更加贴合实际应用场景,灵活性更高。
1、顺序表
底层数据结构:数组
时间复杂度:
- 查找:O(1)
- 插入:O(n)
- 删除:O(n)
具体实现:
public class SequenceList<T> implements Iterable{
//存储元素的数组
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 this.N==0;
}
//获取线性表的长度
public int length(){
return this.N;
}
//获取指定位置的元素
public T get(int i){
return eles[i];
}
//向线性表中添加元素
public void insert(T t){
//如果原来数组不够放了就扩容
if (eles.length == N){
resize(2*N);
}
//将元素放到数组末尾,数组长度加一
eles[N++] = t;
}
//向线性表指定位置添加元素
public void insert(int i, T t){
//如果原来数组不够放了就扩容
if (eles.length == N){
resize(2*N);
}
//指定位置及其后边的元素都后移一位
for(int index=N; index>i; index--){
eles[index] = eles[index-1];
}
//把要插入的元素放到指定位置,数组长度加一
eles[i] = t;
this.N++;
}
//删除指定位置的元素
public T remove(int i){
//缩容
if (N <= eles.length/4){
resize(N/2);
}
//先保存要删除的元素
T tmp = eles[i];
//从该位置开始,后一个元素前移,数组长度减一
for (int index=i; index<N-1; index++){
eles[index] = eles[index+1];
}
N--;
return tmp;
}
//查找元素第一次出现的位置
public int indexOf(T t){
for (int i = 0; i < this.N; i++) {
if (t.equals(eles[i])){
return i;
}
}
return -1;
}
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator{
private int cusor;
public SIterator(){
cusor = 0;
}
@Override
public boolean hasNext() {
return cusor < N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
//调整数组的大小
private void resize(int newSize){
//保存原来数组的信息
T[] temp = eles;
//重新new空间
eles = (T[])new Object[newSize];
//把原来数组的信息保存进新建的空间里
for (int i=0; i<N; i++){
eles[i] = temp[i];
}
}
//测试
public static void main(String[] args) {
SequenceList<String> list = new SequenceList<>(1);
list.insert("lk");
list.insert("hzz");
list.insert("kk");
list.insert(1,"wyj");
System.out.println("下标为0的是:"+list.get(0));
list.remove(2);
for (Object s : list){
System.out.println(s);
}
list.clear();
System.out.println("数组长度为"+list.length());
}
}
2、单向链表
Node
- T item 存储实际的内容
- Node next 指向下一个结点的指针
LinkList
- Node head 头结点(item始终为null,next初始化为空)
- int N (用来存放链表的实际长度)
具体实现:
public class SequenceList<T> implements Iterable{
//存储元素的数组
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 this.N==0;
}
//获取线性表的长度
public int length(){
return this.N;
}
//获取指定位置的元素
public T get(int i){
return eles[i];
}
//向线性表中添加元素
public void insert(T t){
//如果原来数组不够放了就扩容
if (eles.length == N){
resize(2*N);
}
//将元素放到数组末尾,数组长度加一
eles[N++] = t;
}
//向线性表指定位置添加元素
public void insert(int i, T t){
//如果原来数组不够放了就扩容
if (eles.length == N){
resize(2*N);
}
//指定位置及其后边的元素都后移一位
for(int index=N; index>i; index--){
eles[index] = eles[index-1];
}
//把要插入的元素放到指定位置,数组长度加一
eles[i] = t;
this.N++;
}
//删除指定位置的元素
public T remove(int i){
//缩容
if (N <= eles.length/4){
resize(N/2);
}
//先保存要删除的元素
T tmp = eles[i];
//从该位置开始,后一个元素前移,数组长度减一
for (int index=i; index<N-1; index++){
eles[index] = eles[index+1];
}
N--;
return tmp;
}
//查找元素第一次出现的位置
public int indexOf(T t){
for (int i = 0; i < this.N; i++) {
if (t.equals(eles[i])){
return i;
}
}
return -1;
}
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator{
private int cusor;
public SIterator(){
cusor = 0;
}
@Override
public boolean hasNext() {
return cusor < N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
//调整数组的大小
private void resize(int newSize){
//保存原来数组的信息
T[] temp = eles;
//重新new空间
eles = (T[])new Object[newSize];
//把原来数组的信息保存进新建的空间里
for (int i=0; i<N; i++){
eles[i] = temp[i];
}
}
//测试
public static void main(String[] args) {
SequenceList<String> list = new SequenceList<>(1);
list.insert("lk");
list.insert("hzz");
list.insert("kk");
list.insert(1,"wyj");
System.out.println("下标为0的是:"+list.get(0));
list.remove(2);
for (Object s : list){
System.out.println(s);
}
list.clear();
System.out.println("数组长度为"+list.length());
}
}
1) 快慢指针
两个指针:
- 慢指针,每次只走到下一个结点
- 快指针,每次走的是慢指针的两倍
可以解决的问题:
- 求链表中间值
- 判断链表中是否有环
- 判断链表中的环的入口地址
具体实现:
//快慢指针可以解决的问题
public class QSPointer {
//Node结点
private static class Node<T>{
T item;
Node next;
public Node(T item, Node next){
this.item = item;
this.next = next;
}
}
//问题一:获取链表的中间值
//原理:慢指针一次走一步,而快指针每次走的是慢指针的两倍,当快指针走到链表
//最后一个结点时,慢指针刚好到达链表的中部
public static String getMid(Node<String> head){
//定义两个指针
Node<String> fast = head;
Node<String> slow = head;
//遍历两个指针
while (fast != null && fast.next !=null){
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
//问题二:可以判断链表是否有环
//原理:慢指针一次走一步,快指针一次走两步,如果快指针可以和慢指针相遇,那么
//说明这个链表是有环的
public static boolean hasLoop(Node<String> head){
//定义两个指针
Node<String> fast = head;
Node<String> slow = head;
//遍历两个指针
while (fast != null && fast.next !=null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
return true;
}
}
return false;
}
//问题三:找到环的入口元素
//原理:当快慢指针相遇的时候,额外定义一个指针,走的速度和慢指针一样,当额外
//的指针和慢指针相遇的地方,就是环的入口
public static String getEntrance(Node<String> head){
//定义三个指针
Node<String> fast = head;
Node<String> slow = head;
Node<String> temp = null;
//遍历两个指针
while (fast != null && fast.next !=null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
temp = head;
continue;
}
if (temp != null){
temp = temp.next;
if (temp == slow){
return temp.item;
}
}
}
return null;
}
public static void main(String[] args) {
Node<String> seven = new Node<>("gg",null);
Node<String> six = new Node<>("ff",seven);
Node<String> five = new Node<>("ee",six);
Node<String> four = new Node<>("dd",five);
Node<String> three = new Node<>("cc",four);
Node<String> two = new Node<>("bb",three);
Node<String> one = new Node<>("aa",two);
System.out.println("中间值是:"+getMid(one));
seven.next = three;
System.out.println("链表是否有环:"+hasLoop(one));
System.out.println("链表环的入口是:"+getEntrance(one));
}
}
2) 有序符号表
存储key-value键值对,在插入的时候是有序插入的。
具体实现:
public class SymbolTable <Key extends Comparable<Key>, Value> {
//记录首结点
private Node<Key, Value> head;
//记录符号表中键值对的个数
private int N;
//构造方法
public SymbolTable(){
head = new Node(null,null,null);
N = 0;
}
//结点类
private class Node<Key, Value>{
public Key key;
public Value value;
public Node<Key, Value> next;
public Node(Key key, Value value, Node next){
this.key = key;
this.value = value;
this.next = next;
}
}
//根据键key,找对应的值
public Value get(Key key){
Node<Key, Value> node = head;
while (node.next != null){
node = node.next;
if (node.key.equals(key)){
return node.value;
}
}
return null;
}
//向符号表中插入一个键值对
public void put(Key key, Value value){
Node<Key, Value> curr = head.next;
Node<Key, Value> pre = head;
while(curr!=null && key.compareTo(curr.key) > 0){
pre = curr;
curr = curr.next;
}
if (curr!=null && key.compareTo(curr.key)==0){
curr.value = value;
return;
}
Node newNode = new Node(key, value, curr);
pre.next = newNode;
N++;
}
//删除键为key的键值对
public void delete(Key key){
if (N==0){
return;
}
Node<Key, Value> n = head;
while (n.next!=null){
if (n.next.key.equals(key)){
n.next = n.next.next;
N--;
return;
}
n = n.next;
}
}
//获取符号表的大小
public int Size(){
return N;
}
//测试
public static void main(String[] args) {
SymbolTable<Integer, String> table = new SymbolTable<>();
table.put(1,"早起");
table.put(2,"运动");
table.put(3,"冥想");
table.put(9,"读书");
table.put(7,"睡觉");
}
}
3、双向链表
Node
- T item 存储实际的内容
- Node pre 指向上一个结点的指针
- Node next 指向下一个结点的指针
LinkList
- Node first 头结点(pre始终为null, item始终为null,next初始化为空)
- Node last 尾结点(初始化为null)
- int N (用来存放链表的实际长度)
具体实现:
public class TwoWayLinkList<T> implements Iterable{
//头结点
private Node first;
//尾结点
private Node last;
//链表长度
private int N;
@Override
public Iterator iterator() {
return new TwoIterator();
}
private class TwoIterator implements Iterator{
private Node n;
public TwoIterator(){
n = first;
}
@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 pre;
public Node next;
public Node(Node pre, T item,Node next){
this.pre = pre;
this.item = item;
this.next = next;
}
}
//构造方法
public TwoWayLinkList(){
//初始化头结点
this.first = new Node(null, null,null);
//初始化尾结点
this.last = null;
//初始化链表长度
this.N = 0;
}
//空置线性表
public void clear(){
this.first.item = null;
this.first.next = null;
this.first.pre = null;
this.last = null;
this.N = 0;
}
//判断线性表是否为空
public boolean isEmpty(){
return this.N==0;
}
//获取线性表中元素的个数
public int length(){
return this.N;
}
//获取第一个元素
public T getFirsht(){
if (isEmpty()){
return null;
}
return first.next.item;
}
//获取最后一个元素
public T getLast(){
if (isEmpty()){
return null;
}
return last.item;
}
//读取并返回线性表中的第i个元素的值
public T get(int i){
if (i >= N || i < 0){
return null;
}
//可以判断下 i 到头结点近还是到尾结点近
Node n;
if ((N-i)>=(i+1)){
n = first;
for (int index=0; index<=i; index++){
n = n.next;
}
}else{
n = last;
for (int index=0; index<N-1-i; index--){
n = n.pre;
}
}
return n.item;
}
//往线性表中添加一个元素
public void insert(T t){
if (isEmpty()){
//如果链表为空
Node n = new Node(first,t,null);
first.next = n;
last = n;
}else {
//如果链表不为空
Node n = last;
Node newNode = new Node(n,t,null);
n.next = newNode;
last = newNode;
}
N++;
}
//在线性表的第i个元素之前插入一个值为t的数据元素
public void insert(int i, T t){
if (i >= N || i < 0){
return;
}
Node pre;
//判断是从头往后找快还是从后往前找快
if ((N-i)>=(i+1)){
pre = first;
for (int index=0; index<=i-1; index++){
pre = pre.next;
}
}else{
pre = last;
for (int index=0; index<N-2-i; index++){
pre = pre.pre;
}
}
Node curNode = pre.next;
Node newNode = new Node(pre, t, curNode);
pre.next = newNode;
curNode.pre = newNode;
N++;
}
//删除并返回线性表中第i个数据元素
public T remove(int i){
if (i>=N || i<0){
return null;
}
Node pre;
//判断是从头往后找快还是从后往前找快 N=4 i=2
if ((N-i)>=(i+1)){
pre = first;
for (int index=0; index<=i-1; index++){
pre = pre.next;
}
}else{
pre = last;//3
for (int index=0; index<N-i; index++){
pre = pre.pre;
}
}
Node curNode = pre.next;
if (!curNode.equals(last)){
Node laterNode = curNode.next;
pre.next = laterNode;
laterNode.pre = pre;
}else{
pre.next = null;
if (pre.equals(first)){
last = null;
}else{
last = pre;
}
}
N--;
return curNode.item;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
public int indexOf(T t){
Node tmp = first;
for (int index=0; tmp.next!=null; index++){
tmp = tmp.next;
if (tmp.item.equals(t)){
return index;
}
}
return -1;
}
//测试
public static void main(String[] args) {
TwoWayLinkList<String> list = new TwoWayLinkList<>();
list.insert("lk");
System.out.println("链表是不是空的:"+list.isEmpty());
list.insert("hzz");
list.insert("kk");
list.insert(2,"wyj");
for (Object s : list){
System.out.println(s);
}
System.out.println("第一个:"+list.getFirsht());
System.out.println("最后一个:"+list.getLast());
System.out.println("下标为0的是:"+list.get(0));
list.remove(2);
for (Object s : list){
System.out.println(s);
}
System.out.println("链表长度为"+list.length());
System.out.println("下标为2的值是:"+list.get(2));
list.clear();
System.out.println("清空后链表长度为"+list.length());
}
}
5、循环链表
public class JosephProblem {
//内部结点类
private static class Node<T>{
public T item;
public Node next;
public Node(T item, Node next){
this.item = item;
this.next = next;
}
}
//约瑟夫问题:有41个人围成一个圈,第一个人从1开始报数,报到3,这个人就godie,然后下一个再从1开始报数
//直到所有人godie。约瑟夫和朋友不想godie,于是约瑟夫让他自己和朋友分别选择16和31的位置。
public static void main(String[] args) {
//1.创建循环链表
Node<Integer> first = new Node<>(1,null);
Node<Integer> pre = new Node<>(null,null);
for (int i = 1; i <= 41; i++) {
if (i==1){
pre = first;
continue;
}
Node<Integer> newNode = new Node<>(i, null);
pre.next = newNode;
pre = newNode;
if (i == 41){
pre.next = first;
}
}
//2.创建一个计数器
int counts = 0;
Node n = first;
Node preNode = null;
//3.遍历循环链表
while(n.next != n){
counts++;
if (counts == 3){
//删除该结点
preNode.next = n.next;
counts = 0;
System.out.print(n.item+" ");
n = n.next;
}else{
//计数器加一,结点往后移
preNode = n;
n = n.next;
}
}
System.out.print(n.item);
}
}
6、栈
可以使用数组实现,也可以使用链表实现,这里采用的是链表实现。
栈的特点是:先进后出。
入栈:新建一个结点放到head结点后边
出栈:head结点的下一个结点出栈
具体实现:
public class Stack<T> implements Iterable{
//头结点
private Node head;
//链表长度
private int N;
public Stack(){
head = new Node(null,null);
N = 0;
}
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator{
private Node node;
public SIterator(){
node = head;
}
@Override
public boolean hasNext() {
return node.next!=null;
}
@Override
public Object next() {
node = node.next;
return node.item;
}
}
//内部结点类
private class Node<T>{
public T item;
public Node next;
public Node(T item, Node next){
this.item = item;
this.next = next;
}
}
//判断栈是否是空
public boolean isEmpty(){
return N==0;
}
//获取栈中元素的个数
public int size(){
return N;
}
//弹出栈顶元素
public T pop(){
//判断栈是不是空的
if (isEmpty()){
//如果是空的,返回null
return null;
}else {
Node<T> node = head.next;
//判断结点是不是只有一个
if (N==1){
head.next = null;
}else{
Node<T> firstNode = head.next.next;
head.next = firstNode;
}
N--;
return node.item;
}
}
//向栈中压入元素t
public void push(T t){
//判断栈是不是空的
if (isEmpty()){
//如果是空的,直接把元素放到第一个位置
Node<T> newNode = new Node<>(t, null);
head.next = newNode;
}else {
//保存原来第一个位置的结点
Node oldFirst = head.next;
//新建一个结点
Node<T> newNode = new Node<>(t, oldFirst);
//头结点指向新结点
head.next = newNode;
}
N++;
}
//测试
public static void main(String[] args) {
Stack<String> s = new Stack<>();
s.push("我可以");
s.push("我能够");
s.push("一定行");
s.push("相信你");
for (Object str: s) {
System.out.println(str);
}
System.out.println("弹出来的是:"+s.pop());
System.out.println("弹出来的是:"+s.pop());
System.out.println("弹出来的是:"+s.pop());
System.out.println("栈的长度是:"+s.size());
System.out.println("栈是不是空的:"+s.isEmpty());
System.out.println("弹出来的是:"+s.pop());
System.out.println("栈是不是空的:"+s.isEmpty());
}
}
1) 括号匹配
如果是( 就入栈,如果是 )就出栈。
最后可能的结果有:
- 左括号多 遍历完栈不为空
- 右括号多 pop前栈已经空了
- 匹配 pop前栈不为空,遍历完栈为空
public class BracketsMatch {
public static void main(String[] args) {
String str = "(上海((长安)))";
boolean match = isMatch(str);
System.out.println(str+"中的括号是否匹配:"+match);
}
//判断str中的括号是否匹配 有三种结果:匹配成功、左括号多、右括号多
public static boolean isMatch(String str){
//定义一个栈,用来保存左括号
Stack<String> myStack = new Stack<>();
//将传入的字符串解析成一个个的字符串
//遍历字符串数组
for (int i=0; i<str.length(); i++){
String tmp = str.charAt(i)+"";
//判断如果是左括号就入栈,如果是右括号就出栈
if (tmp.equals("(")){
myStack.push(tmp);
}else if (tmp.equals(")")){
String pop = myStack.pop();
if (pop == null){
return false;
}
}
}
if (myStack.size()==0){
return true;
}
//循环结束后判断栈是不是空,是空则匹配成功,不是空则匹配失败
return false;
}
}
2) 逆波兰表达式
public class ReversePolishNotation {
public static void main(String[] args) {
//中缀表达式 3*(17-15)+18/6 的逆波兰式表达式如下
String[] notation = {"3","17","15","-","*","18","6","/","+"};
int result = caculate(notation);
System.out.println("逆波兰表达式的结果为:"+result);
}
public static int caculate(String[] notation){
//定义一个栈,用于存放操作数
Stack<String> myStack = new Stack<>();
//遍历字符串数组
for (int i=0; i<notation.length; i++){
Integer p1;
Integer p2;
switch (notation[i]){
//如果是+-*/ 则弹出栈顶的两个元素,并进行计算,结果再压入栈中
case "+":
p1 = Integer.parseInt(myStack.pop());
p2 = Integer.parseInt(myStack.pop());
myStack.push(p2+p1+"");
break;
case "-":
p1 = Integer.parseInt(myStack.pop());
p2 = Integer.parseInt(myStack.pop());
myStack.push(p2-p1+"");
break;
case "*":
p1 = Integer.parseInt(myStack.pop());
p2 = Integer.parseInt(myStack.pop());
myStack.push(p2*p1+"");
break;
case "/":
p1 = Integer.parseInt(myStack.pop());
p2 = Integer.parseInt(myStack.pop());
myStack.push(p2/p1+"");
break;
default:
//如果是其他的直接入栈
myStack.push(notation[i]);
break;
}
}
if (myStack.size() == 1){
return Integer.parseInt(myStack.pop());
}
return -1;
}
}
9、队列
同样可以用数组实现,也可以用链表实现,这里用链表实现。
队列的特点是:先进先出
入队:每次往链表最后插入元素
出队:每次head下一个元素出队
具体实现:
public class Queue <T> implements Iterable<T>{
//当前队列的元素个数
private int N;
//记录首结点
private Node head;
//记录尾结点
private Node last;
public Queue(){
this.head = new Node(null,null);
this.last = null;
this.N = 0;
}
@Override
public Iterator<T> iterator() {
return new QIterator();
}
private class QIterator implements Iterator<T>{
private Node<T> node;
public QIterator(){
node = head;
}
@Override
public boolean hasNext() {
return node.next!=null;
}
@Override
public T next() {
node = node.next;
return node.item;
}
}
//结点类
private class Node<T>{
public T item;
public Node next;
public Node(T item,Node next){
this.item = item;
this.next = next;
}
}
//判断队列是否为空
public boolean isEmpty(){
return N==0;
}
//获取队列中的元素个数
public int size(){
return N;
}
//从队列中拿出一个元素
public T dequeue(){
if (isEmpty()){
return null;
}
Node<T> firstNode = head.next;
if (N==1){
head.next = null;
last = null;
}else{
head.next = firstNode.next;
}
N--;
return firstNode.item;
}
//往队列中插入一个元素
public void enqueue(T t){
//判断队列是不是空的,即last是不是null
if (last == null){
last = new Node(t,null);
head.next = last;
}else {
Node oldNode = last;
last = new Node(t, null);
oldNode.next = last;
}
N++;
}
public static void main(String[] args) {
Queue<String> q = new Queue<>();
q.enqueue("你很棒!");
q.enqueue("你最强!");
q.enqueue("你最好!");
q.enqueue("牛逼!");
for (String str : q){
System.out.println(str);
}
System.out.println("------------------------------------------");
String result = q.dequeue();
System.out.println("出队列的元素是:"+result);
System.out.println("剩余的元素个数:"+q.size());
}
}

浙公网安备 33010602011771号