数据结构与算法(二)—— 线性表(一)

目录

目录

1、线性表概述

2、顺序表测试

3、顺序表容量可变

1)扩容

2)缩容

4、顺序表的时间复杂度

5、链表

6、单向链表

7、双向链表

8、链表时间复杂度分析

9、单向链表反转

10、快慢指针

1、中间值问题

2、单向链表是否有环问题

3、有环的入口问题

11、循环链表

12、约瑟夫问题


1、线性表概述

线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。

例:A    B    C     D

前驱元素:A在B前面,则称A是B的前驱元素

后继元素:B在A后面,则称B是A的后继元素

线性表的特征:数据元素之间具有一种“一对一”的逻辑关系。

1 . 第一个数据元素没有前驱,这个数据元素被称为头结点

 2. 最后一个数据元素没有后继,这个数据元素被称为尾结点

3. 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继

线性表的分类:

线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。

2、顺序表测试

顺序表:查询快,增删慢(ArrayList)

SequenceList:

package xianxing;

import java.util.Iterator;

//创建顺序表容器
public class SequenceList<T> implements Iterable<T>{

	//存储元素的数组
	private T[] arr;
	//记录当前线性表的元素个数
	private int N;
	
	public SequenceList(int capacity){
		this.arr = (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 arr[i];
	}
	
	//向线性表中添加元素
	public void insert(T t) {
		arr[N++] = t;
	}
	
	//在指定索引处插入数据
	public void insert(int i, T t) {
		for(int j=N; j>i; j--) {
			arr[j] = arr[j-1];
		}
		arr[i] = t;
		N++;
	}
	
	//删除指定索引处的元素,并返回该元素
	public T remove(int i) {
		T m = arr[i];
		for(int j=i; j<N-1; j++) {
			arr[j] = arr[j+1];
		}
		return m;
	}
	
	//查找元素t第一次出现的位置
	public int indexof(T t) {
		for(int j=0; j<N-1; j++) {
			if(arr[j].equals(t)) {
				return j;
			}
		}
		return -1;
	}

	//遍历
	@Override
	public Iterator<T> iterator() {
		return new SIterator();
	}
	
	private class SIterator implements Iterator{
		//定义一个指针
		private int cusor;
		public SIterator() {
			this.cusor = 0;
		}
		@Override
		public boolean hasNext() {
			return cusor<N;
		}

		@Override
		public Object next() {
			return arr[cusor++];
		}
		
	}
}

SequenceTest

package xianxing;


//顺序表容器测试
public class SequenceTest {

	public static void main(String[] args) {
		SequenceList<String> sl = new SequenceList<>(10);
		System.out.println("是否为空表"+sl.isEmpty());
		sl.insert("李四");
		sl.insert("王五");
		sl.insert("李四");
		sl.insert(0, "张三");
		
		for(String s : sl) {
			System.out.println(s);
		}
		
		System.out.println("第一个元素为:"+sl.get(0));
		System.out.println("删除的元素为:"+sl.remove(0));
		System.out.println("'李四'第一次出现的位置为:"+sl.indexof("李四"));

	}

}

3、顺序表容量可变

存在的问题:当元素数量超过容量时,会报错。

SequenceList新增代码

//根据newSize,重置arr大小
	public void reSize(int newSize) {
		//定义一个临时数组,指向arr
		T[] temp = arr;
		arr = (T[]) new Object[newSize];
		for(int i=0; i<N; i++) {
			arr[i] = temp[i];
		}
	}

1)扩容

当前元素数量达到容量最大值,新建一个数组,容量是原来的2倍,将原来的元素放进去。

修改:

//向线性表中添加元素
	public void insert(T t) {
		if(N==arr.length) {
			reSize(arr.length*2);
		}
		arr[N++] = t;
	}
	
	//在指定索引处插入数据
	public void insert(int i, T t) {
		if(N==arr.length) {
			reSize(arr.length*2);
		}
		for(int j=N; j>i; j--) {
			arr[j] = arr[j-1];
		}
		arr[i] = t;
		N++;
	}

2)缩容

当前元素数量小于容量的1/4,容量缩小到原来的1/2

//删除指定索引处的元素,并返回该元素
	public T remove(int i) {
		T m = arr[i];
		for(int j=i; j<N-1; j++) {
			arr[j] = arr[j+1];
		}
		N--;
		if(N<arr.length/4) {
			reSize(arr.length/2);
		}
		return m;
	}

4、顺序表的时间复杂度

get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,间复杂为O(n);

remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);

由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显

5、链表

增删改快

链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。

结点类实现:

public class Node<T> {
		//存储元素
		public T item;
		//指向下一个结点
		public Node next;
		
		public Node(T item, Node next) {
				this.item = item;
				this.next = next;
		}
	}

生成链表:

public static void main(String[] args) throws Exception {
		//构建结点
		Node<Integer> first = new Node<Integer>(11, null);
		Node<Integer> second = new Node<Integer>(13, null);
		Node<Integer> third = new Node<Integer>(12, null);
		Node<Integer> fourth = new Node<Integer>(8, null);
		Node<Integer> fifth = new Node<Integer>(9, null);
		//生成链表
		first.next = second;
		second.next = third;
		third.next = fourth;
		fourth.next = fifth;
	}

6、单向链表

单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据指针域指向第一个真正存储数据的结点

LinkList

package xianxing;

import java.util.Iterator;

public class LinkList<T> implements Iterable<T>{

	//记录头结点
	private Node<T> head;
	//记录链表长度
	private int N;
	
	private class Node<T>{
		T item;
		Node<T> next;
		
		public Node(T item, Node<T> next) {
			this.item = item;
			this.next = next;
		}
	}
	
	public LinkList() {
		head = new Node<T>(null, null);
		N = 0;
	}
	
	//清空:断开头结点的连接,同时N=0
	public void clear() {
		head.next = null;
		N = 0;
	}
	
	//获取链表的长度
	public int length() {
		return N;
	}
	
	//链表是否为空
	public boolean isEmpty() {
		return N==0;
	}
	
	//获取指定位置i的元素
	public Object get(int i) {
		Node<T> n = head.next;
		for(int j=0; j<i; j++) {
			n = n.next;
		}
		return n.item;
	}
	
	//插入元素
	public void insert(T t) {
		Node<T> n = head;
		while(n.next != null) {
			n = n.next;
		}
		Node<T> m = new Node<T>(t, null);
		n.next = m;
		N++;
	}
	
	//在指定位置i插入新的元素
	public void insert(int i, T t) {
		Node<T> n = head;
		for(int j=0; j<i-1; j++) {
			n = n.next;
		}
		Node<T> m = new Node<T>(t, n.next);
		n.next = m;
		N++;
	}
	
	//删除元素,并返回该元素
	public Object remove(int i) {
		Node<T> n = head;
		for(int j=0; j<i-1; j++) {
			n = n.next;
		}
		Node<T> m = n.next;
		n = m.next;
		N--;
		return m.item;
	}
	
	//查找元素t第一次出现的位置
	public int indexOf(T t) {
		Node<T> n = head;
		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 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;
		}
		
	}
}

LinkTest

package xianxing;


//顺序表容器测试
public class LinkTest {

	public static void main(String[] args) {
		LinkList<String> sl = new LinkList<String>();
		System.out.println("是否为空表"+sl.isEmpty());
		sl.insert("李四");
		sl.insert("王五");
		sl.insert("李四");
		sl.insert(0, "张三");
		
		for(String s : sl) {
			System.out.println(s);
		}
		
		System.out.println("第一个元素为:"+sl.get(0));
		System.out.println("删除的元素为:"+sl.remove(0));
		System.out.println("'李四'第一次出现的位置为:"+sl.indexOf("李四"));

	}

}

7、双向链表

LinkedList

双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。

TwoLinkList

package xianxing;


import java.util.Iterator;

public class TwoLinkList<T> implements Iterable<T>{

	//记录头结点
	private Node<T> head;
	//记录链表长度
	private int N;
	//记录尾结点
	private Node<T> last;
	
	private class Node<T>{
		T item;
		//指向上一个结点
		Node<T> pre;
		Node<T> next;
		
		public Node(T item, Node<T> next, Node<T> pre) {
			this.item = item;
			this.next = next;
			this.pre = pre;
		}
	}
	
	public TwoLinkList() {
		this.head = new Node<T>(null, null, null);
		this.last = null;
		N = 0;
	}
	
	//清空:断开头结点的连接,同时N=0
	public void clear() {
		this.head.next = null;
		this.last = null;
		  
		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;
	}
	//获取指定位置i的元素
	public Object get(int i) {
		Node<T> n = head.next;
		for(int j=0; j<i; j++) {
			n = n.next;
		}
		return n.item;
	}
	
	//插入元素
	public void insert(T t) {
		if(isEmpty()) {
			//如果为空
			Node<T> newNode = new Node<T>(t, null, head);
			//新结点成为尾结点
			last = newNode;
			//让头结点指向新结点
			head.next = newNode;
		}else {
			Node<T> newNode = new Node<T>(t, null, last);
			last.next = newNode;
			last = newNode;
		}
		N++;
		
	}
	
	//在指定位置i插入新的元素
	public void insert(int i, T t) {
		Node<T> n = head;
		for(int j=0; j<i-1; j++) {
			n = n.next;
		}
		Node<T> m = new Node<T>(t, n.next,n);
		n.next.pre = m;
		n.next = m;
		N++;
	}
	
	//删除元素,并返回该元素
	public Object remove(int i) {
		Node<T> n = head;
		for(int j=0; j<i-1; j++) {
			n = n.next;
		}
		Node<T> m = n.next;
		n.next = m.next;
		m.next.pre = n;
		N--;
		return m.item;
	}
	
	//查找元素t第一次出现的位置
	public int indexOf(T t) {
		Node<T> n = head;
		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 SIterator();
	}
	
	private class SIterator implements Iterator{

		private Node<T> n;
		public SIterator() {
			this.n = head;
		}
		@Override
		public boolean hasNext() {
			
			return n.next != null;
		}

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

8、链表时间复杂度分析

get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)

insert(int i,T t):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);

remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)

相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表增删操作比较多,建议使用链表

 

9、单向链表反转

LinkList中添加

//对整个链表进行反转
    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;
    }

10、快慢指针

1、中间值问题

快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。

如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。

package xianxing;

//测试类
public class Test {
    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);
    }

    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> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        private Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

2、单向链表是否有环问题

问题:

方法:

package xianxing;


//有环问题
public class Test2 {
    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){
            slow = slow.next;
            fast = fast.next.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;
        }
    }
}

3、有环的入口问题

找到有环的入口

方法:定义三个指针,快、慢、临时指针,fast、slow每相遇一次,判断fast 或 slow有没有与 temp相遇,相遇就结束遍历,没有则temp向后移。

package xianxing;

public class Test3 {
    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);
    }
    //查找有环链表中环的入口结点
    public static Node getEntrance(Node<String> first) {
        Node<String> fast = first;
        Node<String> slow = first;
        Node<String> temp = first;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(temp.equals(fast) || temp.equals(slow)){
                break;
            }
            if(fast.equals(slow)){
                temp = temp.next;
            }
        }
        return temp;
    }
    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

11、循环链表

循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。

package xianxing;

public class CycleList {
    public static void main(String[] args) {
        //构建结点
        Node<Integer> first = new Node<Integer>(1, null);
        Node<Integer> second = new Node<Integer>(2, null);
        Node<Integer> third = new Node<Integer>(3, null);
        Node<Integer> fourth = new Node<Integer>(4, null);
        Node<Integer> fifth = new Node<Integer>(5, null);
        Node<Integer> six = new Node<Integer>(6, null);
        Node<Integer> seven = new Node<Integer>(7, null);
        //构建单链表
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //构建循环链表,让最后一个结点指向第一个结点
        seven.next = first;
        
    }

    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
        
}

12、约瑟夫问题

41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。

1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;

2.自退出那个人开始的下一个人再次从1开始报数,以此类推;

3.求出最后退出的那个人的编号。

思路:

1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;

2.使用计数器count,记录当前报数的值,pre记录上一个值;

3.遍历链表,每循环一次,count++;

4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;

package xianxing;

public class JosephTest {
    public static void main(String[] args) {
        Node<Integer> first = new Node<>(1,null);
        Node<Integer> pre = first;
        for(int i=2; i<=41; i++){
            Node<Integer> newNode = new Node<>(i,null);
            pre.next = newNode;
            pre = newNode;
            if(i == 41){
                pre.next = first;
            }
        }

        Node<Integer> n = first;
        int count = 0;
        while(n != n.next){
            count++;
            if(count == 3){
                pre.next = n.next;
                count = 0;
            }
            pre = n;
            n = n.next;
        }
        System.out.println(n.item);
    }

    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值