2020-11-18

线性数据结构:从物理存储上分为顺序结构、链式结构。具体包含数组、链表、栈、队列四种线性结构。
一、数组:顺序存储的线性结构,它占用连续内存空间,存储相同数据类型的数据。
1、特点:
利于查找(直接通过下标查询);不利于插入、删除,其时间复杂度为O(n)。

数组的定义:
	int a[] = new int[5];//初始化时必须定义空间大小
	int a[] = new int[] {1,2,3};//直接在定义时赋值
	
数组的插入操作:【在数组a的第i个位置插入元素x】
	for(int j = a.length ; j > i ;j--){//元素集体后移
		a[j+1] = a[j];
	}
	a[i] = x;//在i位置插入该元素
	a.length + 1;//数组长度加一
	
数组的删除操作:【删除数组a的第i个元素】
	for(int j = i+1 ; j < a.length ; j++){ //元素集体前移
		a[j-1] = a[j];
	}
	a.length - 1;//数组长度减一
	

2、针对数组类型,Java提供了容器类ArrayList,其底层采用数组实现,封装了对数组的增、删、改、查操作细节。此外ArrayList最大优势是支持动态扩容。每次当存储空间不够时,ArrayList自动扩容1.5倍,默认可以存10个元素,但是这会涉及到内存的申请和数据的迁移。

ArrayList操作:
	初始化 List<String> list = new ArrayList();
【泛型不能是基本数据类型,可以是它们的包装类】
	添加元素 add(E e)、add(int index,E e)
	删除元素 remove(int index)、
	获取元素 get(int index)
	得到数组长度 size()
	修改元素 set(int index,E e)
	获得元素在数组中首次出现的位置 indexOf(E e)
	获得元素在数组中最后一次出现的位置 lastIndexOf(E e)

ArrayList源码分析:
	public class ArrayList<E> extends AbstractList<E>
        				   implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
   
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
    transient Object[] elementData; // 存放数组元素
    private int size;//数组容量大小
    
//添加元素:
 	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//数组添加第一个元素时,minCapacity = 10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) { //扩容方法
	  
        int oldCapacity = elementData.length;//当前数组容量(旧容量)
        int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量 = 旧容量 * 1.5 
        
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //将原数组复制进扩容后的数组
        elementData = Arrays.copyOf(elementData, newCapacity);
	}	   	   
} 

二、链表:链式存储的线性结构,占用一组离散的内存空间,存储相同数据类型的数据。链表由若干个结点组成,每个结点有数据域和指针域两部分。
1、特点:易于插入、删除;不利于查询,其时间复杂度为O(n)。
2、链表的分类:单链表、循环单链表、双链表、循环双链表。

单链表的定义:
public class Node {
	int data;//数据域
	Node next;//指针域,指向后继结点
	public Node(int data) {
		this.data = data;
	}
}
单链表的插入
	/**
	 * 尾插法插入链表:遍历整个链表找到最后一个节点再插入
	 */
	public void insertNode(int data) {
		Node newNode = new Node(data);
		if (head == null) {// 头结点head为空
			head = newNode;
			return;
		}

		Node node = head;
		while (node.next != null) { // 遍历链表找到尾结点
			node = node.next;
		}
		node.next = newNode;// 在尾结点后插入
	}
	
	/**
	 * 头插法插入链表:将新节点插入到head前面,并将新节点令为head
	 */
	public void insertHead(int data) {
		Node newNode = new Node(data);
		if (head == null) {
			head = newNode;
			return;
		}
		newNode.next = head;//插入结点指向头结点
		head = newNode;//插入结点赋值为头结点
	}

	/**
	*在链表中间插入结点:如结点preNode后面插入newNode
	*/
	newNode.next =preNode.next ;
	preNode.next = newNode;
	
单链表的删除
	/**
	 * 删除第index个节点:删除结点必须知道要删除结点的前驱
	 */
	public boolean deleteNode(int index) {
		if (index == 1) {// 单链表只有一个结点
			head = head.next;
			return true;
		}
		Node preNode = head;
		Node curNode = head.next;
		int i = 1;
		while (curNode != null) {
			i++;
			if (index == i) {
				preNode.next = curNode.next;
				return true;
			}
			preNode = preNode.next;
			curNode = curNode.next;
		}
		return true;
	}


单链表的遍历:
	/**
	 * 展示链表:不断查找当前的结点的后继
	 */
	public void display() {
		if (head == null) {
			System.out.println("当前链表无节点,请插入");
			return;
		}
		Node node = head;//从头结点开始
		while (node != null) {
			System.out.print(node.data + "  ");
			node = node.next;
		}
	}

3、针对链表类型,Java提供LinkedList,封装了对链表各种操作细节。

LinkedList操作
	初始化:List<String> list = new LinkedList();
【泛型不能是基本数据类型,可以是它们的包装类】
【LinkedList是双链表,在源码分析中可以看见!!!】
	添加 addFirst(E e)、addLast(E e)、add(E e)、add(int index,E e)
	删除 remove(E e)、removeFirst()、removeLast()、remove(int index)
	获取元素 getFirst()、getLast()、get(int index)
	长度 size()
	

LinkedList源码分析:
	public class LinkedList<E> extends AbstractSequentialList<E>
    					   implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;
    transient Node<E> first;//头结点
    transient Node<E> last;//尾结点
  
	private static class Node<E> {//静态内部类:定义结点
	    E item;//数据域
	    Node<E> next;//后继
	    Node<E> prev;//前驱
	
	    Node(Node<E> prev, E element, Node<E> next) {
	        this.item = element;
	        this.next = next;
	        this.prev = prev;
	    }
	}

//尾插法插入数据:
	public boolean add(E e) {
        linkLast(e);
        return true;
    }
    void linkLast(E e) {
        final Node<E> l = last;//尾结点给l
        final Node<E> newNode = new Node<>(l, e, null);
        
        last = newNode;//插入结点为新的尾结点
        
        if (l == null)//链表为空
            first = newNode;
        else
            l.next = newNode;
            
        size++;
        modCount++;
    }

//获取元素:
 	public E get(int index) {
        checkElementIndex(index);//检查索引是否合理
        return node(index).item;//返回索引对应的元素
    }
    
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
    
    Node<E> node(int index) {
       
        if (index < (size >> 1)) {//如果索引在链表的前一半,从头结点向后查找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {	//如果索引在链表的后一半,从尾结点向前查找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

	

三、题目:【可以考虑双指针思想】

1、ArrayList与LinkedList区别:
	1)ArrayList是基于数组实现的,支持随机访问,但是插入删除效率不如LinkedList。LinkedList是基于双链表实现的,支持顺序访问,适合频繁插入删除。
	2)LinkedList比ArrayList更占内存,因为它除了存储数据,还要存储指针。

2、反转单链表:
	/*
	 * 从前往后遍历反转单链表:head为头结点
	 */
	public static Node reverse(Node head) {
		// 如果单链表为空或只有一个结点,不用反转
		if (head == null || head.next == null) {
			System.out.println("单链表为空或只有一个结点,不用反转");
			return head;
		}

		Node node = head;
		Node pre = null;

		while (node != null) {
			Node temp = node.next;// temp临时存储下一结点
			node.next = pre;
			pre = node;

			node = temp;// 最后temp给node
		}
		return pre;// 返回反转后的单链表
	}
示例图如下:

3、找出单向链表中倒数第 k 个节点
	/*
	 * 单链表head中倒数第k个结点(k有效)的值:
	 * 双指针pre、cur:其中cur为head向后走k步,此时pre和cur同走直到链表尾部。pre即为倒数第k结点
	 */
	public static int findK(Node head, int k) {
		Node pre = head;
		Node cur = head;
		for (int i = 0; i < k; i++) {
			cur = cur.next;
		}
		while (cur != null) {
			cur = cur.next;
			pre = pre.next;
		}
		return pre.data;
	}

4、删除单链表的中间结点
	/*
	 * 删除链表的中间结点(链表长度至少为2)
	 * 双指针:pre慢指针,每一跳一步;cur快指针,每次两步。
	 */
	public static Node deleteNode(Node head) {
		if (head == null || head.next == null) {
			System.out.println("链表为空或只有一个结点,不用删除");
			return head;
		}

		if (head.next.next == null) {
			System.out.println("只有两个结点,直接删除第一个即可");
			head = head.next;
			return head;
		}

		Node pre = head;
		Node cur = head;
		Node temp = null;
		while (cur.next != null && cur.next.next != null) {
			cur = cur.next.next;// 每次跳两步
			temp = pre;
			pre = pre.next;// 每次跳一步
		}
		// pre就是中间结点,temp是其前驱
		temp.next = pre.next;
		return head;
	}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值