顺序表和链表的基本操作(Java实现)

线性表
头插/尾插/中间插入   头删/尾删
1.顺序表
  想细节,写伪代码。
难点:循环边界
2.链表(*****)
结点和结点之间的逻辑关系

一、顺序表

逻辑上有前后关系,物理存储也是前后相连的。

1、插入操作

头插
思考:(伪代码)
1.需要后移size个元素
2.为避免数据被覆盖,从后往前移
3.空间[size,1]  数据[size-1,0]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1

插入:(在下标为index的位置上插入item)
思考:(伪代码)
1.需要后移size-index个元素
2.为避免数据被覆盖,从后往前移
3.空间[size,index+1]  数据[size-1,index]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1

在插入和头插的时候,要注意从后往前插

尾插:直接插入

2、删除操作

头删
1.需要移动size-1个数据
2.为避免数据覆盖,从前往后移。
3.空间[0,size-1],数据[1,size]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1
删除:下标是index的数据
1.需要移动size-index-1个数据
2.为避免数据覆盖,从前往后移。
3.空间[index,size-2],数据[1+index,size-1]
4.array[空间的下标]=array[数据的下标]
5.空间的下标=数据的下标+1

3、时间复杂度

	尾插/尾删		O(1) 均摊O(1),在扩容时是O(n)
	头插/头删		O(n)  数据搬移是耗时的
	中间插入/删除	      O(n)  数据搬移是耗时的
        查找的时间复杂度         O(1)直接通过下标

4、扩容

假如我们的数组放不下了,扩容
this.array  住的老房子
this.size    房子里住的人
应该搬家    this.size==this.array.length
1)先找新房子
2)搬家
3)this.array=新房子的地址
4)退掉老房子

5、顺序表的代码实现

package com.company.DataStruct.list.arraylist;

public interface IArrayList {
    //增 删

    /**
     * 把item插入到线性表的前面
     * @param item 要插入的数据
     */
    void pushFront(int item);

    /**
     * 把item插入到线性表的最后
     * @param item
     */
    void pushBack(int item);

    /**
     * 把item插到index下标位置处,index后的数据后移
     * @param item
     * @param index
     */
    void add(int item,int index);

    /**
     * 删除前面的数据
     */
    void popFront();

    /**
     * 删除最后的数据
     */
    void popBack();

    /**
     * 删除index处的数据,index后的数据前移
     * @param index
     */
    void remove(int index);

    /**
     * 查找关键字key是否在链表中
     * @param key
     * @return 如果在,返回下标;不在,返回-1.
     */
    int search(int key);

    /**
     * 判断关键字key是否在链表中
     * @param key
     * @return
     */
    boolean contains(int key);

    /**
     * 得到pos位置的值
     * @param pos
     * @return
     */
    int getPos(int pos);

    /**
     * 打印顺序表
     */
    void display();

    /**
     * 清空顺序表
     */
    void clear();
}
package com.company.DataStruct.list.arraylist;

public class MyArrayList implements IArrayList {
    private int[] array;//保存数据的空间
    private int size;//保存有效数据个数(数据规模)

    MyArrayList(int capacity){
        this.array=new int[capacity];
        this.size=0;
    }


    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    /**
     * 返回下标是index的数据
     * @param index
     * @return
     */
    public int getIndex(int index){
        return array[index];
    }
    @Override

    //头插  T(n)=O(n)
    public void pushFront(int item) {
        ensureCapacity();
    //将已有的数据后移一格(从后往前)
        for(int i=this.size;i>0;i--){
            this.array[i]=this.array[i-1];
        }
        this.array[0]=item;
        this.size++;
    }

    @Override
    public void pushBack(int item) {
        ensureCapacity();
        if(this.array.length>this.size) {
            this.array[size++] = item;
        }else{
            System.out.println("空间不够,插入失败");
        }
    }

    @Override
    public void add(int item, int index) {
        ensureCapacity();
//        i代表空间的下标
//        for(int i=this.size;i>index;i--){
//            this.array[i]=this.array[i-1];
//        }
        if(index<0||index>this.size){
            throw new Error();
        }
        //index=0时是最坏情况,
//        i代表数据的下标
        for(int i=this.size-1;i>=index;i--){
            this.array[i+1]=this.array[i];
        }
//        i代表循环的次数
//        for(int i=0;i<this.size-index;i++){
//            this.array[size-i]=this.array[size-i-1];
//        }

        this.array[index]=item;
        this.size++;
    }

    @Override
    public void popFront() {
        for(int i=0;i<this.size-1;i++){
            this.array[i]=this.array[i+1];
        }
        this.size--;
    }

    @Override
    public void popBack() {
        if(this.size==0){
            throw new Error();//抛出异常
        }
        this.size--;
    }

    @Override
    public void remove(int index) {
        if(index<0||index>=this.size){
            throw new Error();
        }
        if(this.size==0){
            throw new Error();
        }
        for(int i=index;i<this.size-1;i++){
            this.array[i]=this.array[i+1];
        }
        this.size--;
    }

    @Override
    public int search(int key) {
        for(int i=0;i<this.array.length;i++){
            if(key==this.array[i]){
                return i;
            }
        }
        return -1;
    }

    @Override
    public boolean contains(int key) {
        for(int i=0;i<this.array.length;i++){
            if(key==this.array[i]){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getPos(int pos) {
        if(pos<0||pos>=this.size){
            throw new Error();
        }
        return this.array[pos];
    }

    @Override
    public void display() {
        for(int i=0;i<this.size;i++){
            System.out.print(this.array[i]+" ");
        }
        System.out.println();
    }

    @Override
    public void clear() {
        for(int i=0;i<this.size;i++){
            this.array[i]=0;
        }
    }

    /**
     * 扩容,保证数组容量够用  T(n)=O(n)
     */
    private void ensureCapacity(){
        if(this.size<this.array.length){//当前容量够用来存放新的数据,直接返回。
            return;
        }
        //1.找新房子,找原来的两倍大。
        int capacity=this.array.length*2;
        int[] newArray=new int[capacity];

        //2.搬家
        for(int i=0;i<this.size;i++){
            newArray[i]=this.array[i];
        }

        //3.登记新房子的位置,推掉老房子
        this.array=newArray;//原来的数组用于没有引用,JDK会自动回收。

    }
}

测试代码:

package com.company.DataStruct.list.arraylist;

import com.company.DataStruct.list.arraylist.MyArrayList;

public class Test {
    public static void main(String[] args) {
        MyArrayList arrayList=new MyArrayList(10);
        arrayList.pushBack(1);
        arrayList.pushBack(2);
        arrayList.pushBack(3);

        arrayList.pushFront(6);
        arrayList.pushFront(5);//5 6 1 2 3

        arrayList.add(4,2);//5 6 4 1 2 3
        for(int i=0;i<arrayList.getSize();i++){
            System.out.print(arrayList.getIndex(i)+" ");
        }
        System.out.println();
        System.out.println("=======================================");
        arrayList.popFront();//6 4 1 2 3
        arrayList.remove(2);//6 4 2 3
        arrayList.popBack();
        System.out.println("=======================================");
        for(int i=0;i<arrayList.getSize();i++){
            System.out.print(arrayList.getIndex(i)+" ");
        }
        System.out.println();
        System.out.println("=======================================");
        System.out.println(arrayList.search(2));//2
        System.out.println("=======================================");
        System.out.println(arrayList.contains(9));//false
        System.out.println(arrayList.getPos(1));//4
        //插入10个数
        for(int i=0;i<10;i++) {
            arrayList.pushBack(i*100);
        }
        //打印
        arrayList.display();
        //清空
        arrayList.clear();
        //打印
        arrayList.display();//10个0 清空之后,数组又变为原来的容量
    }
}

结果:

二、链表

逻辑上有线性关系  但是物理存储不保证连续(前后相连)。
结点类{值,下一个结点的引用}
链表类  第一个结点的引用

1.链表结点间的关系

引用{value,next},如:
@0xFF{1.@0xCC}
@0xCC{2,@0x33}
@0x33{3,@0x99}
@0x99{4,@0xAA}
@0xAA{5,null}
分析下面的每句代码代表什么意思?
Node p1=//@0xFF
Node p2=//@0xAA
Node p3=//0x@CC
p1.next=p2; <=>@0xFF{1,@0xAA}
p2=p1.next;   <=> p2=//@0xCC;
p1.next.next=p2;<=> @0xCC{2,@0xAA}
p1.next.next=p3.next.next;<=> @0xCC{3,@0x99}
p1=p2;<=>p1=@0xAA

2.头删与头插

头插
1)先创建一个结点
2)设置结点的下一个结点是原来的头结点
3)修改原来链表的头结点为新结点
头删
1)首先判断链表是否为空
2)如果链表为空,抛出异常
3)如果链表不为空,则修改原来的头结点为原头结点的下一个结点

3.尾删与尾插

尾插
1)先创建一个结点
2)如果链表为空,则将该结点赋值给链表的头结点。
3)否则获取到链表的最后一个结点,修改最后一个结点的next为新结点。
尾删
1)先判断链表是否为空。
2)如果为空,则直接抛出异常。
3)如果不为空,寻找链表的倒数第二个结点,修改该结点的next为null。
4)如果链表只有一个结点,则将链表的头结点设置为null。

4、链表的代码实现

package com.company.DataStruct.list.linkedlist;

public class MyLinkedList {
    /**
     * 链表中的一个结点(内部类)
     */
    public class Node{
        public int value;//保存的是有效数据
        public Node next;//下一个结点的线索(引用)
        Node(int v){
            this.value=v;
            this.next=null;
        }
    }

    //    如果一个结点都没有,head=null
    private Node head;//保存链表中第一个结点的引用

    public Node getHead() {
        return head;
    }

    MyLinkedList(){
        this.head=null;

    }


    /**
     * 头插  将item值的结点插入到链表的最前面
     * @param item
     */
    public  void pushFront(int item){
        //先创建一个新结点
        Node node=new Node(item);
        node.next=head;
        this.head=node;
    }

    /**
     * 获取链表的最后一个结点
     * @return
     */
    private Node getLast(){
        Node cur=this.head;
        while(cur.next!=null){
            cur=cur.next;
        }
        return cur;
    }
    /**
     * 尾插
     * @param item
     */

    public void pushBack(int item){
        Node node=new Node(item);
        if(this.head==null){
            this.head=node;
        }else{
            Node last=getLast();
            last.next=node;
        }
    }

    /**
     * 头删  T(n)=O(1)
     */
    public  void popFront(){
        if(this.head==null){//链表为空
            throw new Error();
        }
        this.head=this.head.next;
    }

    /**
     * 查找倒数第二个结点
     * @return
     */
    private Node getSecondLast(){
        Node pNode=this.head;
        while(pNode.next.next!=null){
            pNode=pNode.next;
        }
        return pNode;
    }
    /**
     * 尾删
     */
    public  void popBack(){
        if(this.head==null){
            throw new Error();
        }
        if(this.head.next==null){//链表中只有一个结点
            this.head=null;
        }
        Node pNode=getSecondLast();
        //此时pNode是倒数第二个结点
        pNode.next=null;
    }

    /**
     * 如何通过循环,遍历链表的每个结点
     */
    void display(){
       Node cur=this.head;
       while(cur!=null){
           System.out.format("%d-->",cur.value);
           cur=cur.next;
       }
       System.out.println("null");
    }
}

测试代码:

package com.company.DataStruct.list.linkedlist;

public class TestLinkedList {
    public static void main(String[] args) {
        MyLinkedList myLinkedList=new MyLinkedList();
        myLinkedList.pushFront(1);
        myLinkedList.pushFront(2);
        myLinkedList.pushBack(3);
        myLinkedList.pushBack(4);
        myLinkedList.pushBack(5);//2 1 3 4 5
        myLinkedList.popBack();
        myLinkedList.popFront();//1 3 4
        //遍历打印
        myLinkedList.display();
    }
}

结果:

 

顺序表链表是两种常见的数据结构,它们都可以用来实现线性表,但是在实现上有所不同。 1. 顺序表基本操作: - 初始化:创建一个定长的数组,用来存储数据。 - 插入:在指定位置上插入一个元素,需要将该位置及其后面的元素全部后移,时间复杂度为O(n)。 - 删除:删除指定位置上的元素,需要将该位置后面的元素全部前移,时间复杂度为O(n)。 - 查找:根据元素的值或者下标查找指定元素,时间复杂度为O(n)。 - 修改:修改指定位置上的元素,时间复杂度为O(1)。 2. 链表基本操作: - 初始化:创建一个头节点,头节点不存储数据,用来标识链表的起始位置。 - 插入:在指定位置上插入一个元素,需要先找到该位置的前一个节点,然后将该节点的next指针指向新节点,新节点的next指针指向该节点后面的节点,时间复杂度为O(n)。 - 删除:删除指定位置上的元素,需要先找到该位置的前一个节点,然后将该节点的next指针指向该节点后面的节点,时间复杂度为O(n)。 - 查找:根据元素的值或者下标查找指定元素,需要从头节点开始遍历整个链表,时间复杂度为O(n)。 - 修改:修改指定位置上的元素,需要先找到该节点,然后修改该节点存储的数据,时间复杂度为O(n)。 总体而言,顺序表适用于频繁访问元素,而不频繁修改和删除的场景,链表适用于频繁修改和删除元素的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值