算法基础知识--数组、单链表、双向链表

什么是数据结构?
数据结构是数据如何在计算机中进行组织和存储,使得我们可以高效的获取数据或者修改数据。

三种数据结构:线性结构(数组、链表、栈、队列哈希表(也叫做散列表))、树形结构、图形结构。

数组
是一种顺序存储的线性表,所有元素的内存地址是连续的,基于这样的存储方式,数组具备一个很大的优点:快速查询,通过索引可以快速获取到需要查询的数据。
数组具备以下几个概念。

1、长度:数组当前有多少个元素
2、容量:数组最大能够存放多少个元素

疑惑:当我们不确定数组需要存多少个元素怎么办?初始化无法设置具体容量

也可以自己写动态数组

package t;

public class MyOwnArrayList<E> {
	// 元素的数量
	private int size;

	private static final int ELEMENT_NOT_FOUND = -1;

	// 所有的元素,定义为泛型,适应不同数据类型
	private E[] data;

	// 初始容量
	private static final int DEFAULT_CAPACITY = 100;

	public MyOwnArrayList(int capacity) { // 容量小于100一律扩充为100
		capacity = (capacity < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
		data = (E[])new Object[capacity];
	}

	public MyOwnArrayList(){
		this(DEFAULT_CAPACITY);
	}

	/**
	 * 获取数组中的元素数量
	 * @return
	 */
	public int size(){
		return size;
	}

	/**
	 * 判断数组是否为空
	 * @return
	 */
	public boolean isEmpty(){
		return size == 0;
	}

	/**
	 * 判断数组是否包含某个元素
	 * @param element
	 * @return
	 */
	public boolean contains(E element){
		// 如果等于 -1 ,表示数组不包含该元素
		// 如果不等于 -1 ,表示有正确的索引,数组包含该元素
		return indexOf(element) != ELEMENT_NOT_FOUND;
	}

	/**
	 * 添加元素到数组最末端
	 */
	public void add(E element){
		add(size,element);
	}

	/**
	 * 添加元素到索引 index 位置(增)
	 * @param index
	 * @param element
	 */
	public void add(int index, E element){
		// 检查下标越界
		rangeCheckAdd(index);

		// 保证有足够容量
		ensureCapacity(size + 1);
		//     ↓
		// 0 1 2 3 4 5 6 7 8 9 (索引index)
		// 1 2 3 4 5 6 x x x x (原数组)
		// 在 index = 2处,插入 9 ,元素全部后移
		// 1 2 9 3 4 5 6 x x x (add 后数组)
		// 从后往前, 将每个元素往后移一位, 然后再赋值
		for ( int i = size - 1 ; i >= index ; i-- ){
			data[i + 1] = data[i];
		}

		data[index] = element;

		size++;
	}

	/**
	 * 删除索引 index 位置的元素(删)
	 * @param index
	 * @return 删除的元素
	 */
	public E remove(int index){
		rangeCheck(index);
		E ret = data[index];
		for( int i = index + 1 ; i < size ; i++){
			data[i - 1] = data[i];
		}
		size--;
		// 删除元素后, 将最后一位设置为 null
		data[size] = null;
		return ret;
	}

	/**
	 * 获取索引 index 位置的元素(查)
	 * @param index
	 * @return 原来的元素ֵ
	 */
	public E get(int index){
		rangeCheck(index);
		return data[index];
	}

	/**
	 * 设置索引 index 位置的元素(改)
	 * @param index
	 * @param element
	 * @return 原来的元素ֵ
	 */
	public E set(int index, E element){
		rangeCheck(index);
		E ret = data[index];
		data[index] = element;
		return ret;
	}


	/**
	 * 查看某个元素所在的位置
	 * @param element
	 * @return
	 */
	public int indexOf(E element){
		for (int i = 0; i < size; i++) {
			if(data[i] == element) return i;
		}
		return ELEMENT_NOT_FOUND;
	}

	/**
	 * 删除所有元素
	 */
	public void clear(){
		// 使用泛型数组后要注意内存管理(将元素置null)
		for (int i = 0; i < size; i++) {
			data[i] = null;
		}
		size = 0;
	}

	/*
	 * 扩容操作
	 */
	private void ensureCapacity(int capacity){
		int oldCapacity = data.length;
		if(oldCapacity >= capacity) return;
		// 新容量为旧容量的 2 倍
		int newCapacity = oldCapacity * 2; // 1.5
		E[] newElements = (E[])new Object[newCapacity];
		for (int i = 0; i < size; i++) {
			newElements[i] = data[i];
		}
		data = newElements;
	}

	// 下标越界抛出的异常
	private void outOfBounds(int index) {
		throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
	}
	// 检查下标越界(不可访问或删除size位置)
	private void rangeCheck(int index){
		if(index < 0 || index >= size){
			outOfBounds(index);
		}
	}
	// 检查add()的下标越界(可以在size位置添加元素)
	private void rangeCheckAdd(int index) {
		if (index < 0 || index > size) {
			outOfBounds(index);
		}
	}

	@Override
	public String toString() {
		StringBuilder string = new StringBuilder();
		string.append("size=").append(size).append(", [");
		for (int i = 0; i < size; i++) {
			if(0 != i) string.append(", ");
			string.append(data[i]);
		}
		string.append("]");
		return string.toString();
	}

}

动态数组中执行 插入 和 删除 操作需要移动大量的元素?
动态数组可能会造成内存空间的大量浪费?
链表

链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的,因为只有在每次新增数据的时候才会去申请内存地址,它是真正的动态数据结构。
结构如图
在这里插入图片描述
优点:真正的动态,不需要处理固定容量的问题
缺点,不能像像数组那样,用索引获取元素,随机访问
自定义单链表

package test;

public class MyLinkedList<E> {

	private class Node{
		// 1、当前节点存储的值
		public  E e;

		// 2、当前节点存储的执行下一个节点的地址
		public  Node next;

		public Node(E e, Node next){
			this.e = e;
			this.next = next;
		}

		public Node(E e){
			this(e, null);
		}

		public Node(){
			this(null, null);
		}
	}

	// 元素的数量
	private int size;

	private Node dummyHead;

	public MyLinkedList(){
		dummyHead = new Node();
		size = 0;
	}

	// 1、获取链表中的元素个数
	public  int getSize(){
		return size;
	}

	// 2、判断链表是否为空
	public boolean isEmpty(){
		return  size == 0;
	}

	// 3、在链表头添加元素
	public void addFirst(E e){
		add(0 ,e);
	}

	// 4、在链表中间添加元素
	public void add(int index ,E e){

		Node prev = dummyHead;

		// 需要遍历 index - 1 次,找到 prev 所在的位置
		for( int i = 0 ; i < index  ; i++){
			prev = prev.next;
		}

		Node node = new Node(e);

		node.next = prev.next;

		prev.next = node;

		size++;
	}

	// 5、在链表尾部添加元素
	public void addLast(E e){
		add(size,e);
	}

	// 6、获取链表的第 index 个位置的元素
	public E get(int index) {

		Node cur = dummyHead;

		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}
		return cur.e;
	}

	// 获得链表的第一个元素
	public E getFirst(){
		return get(0);
	}

	// 获得链表的最后一个元素
	public E getLast(){
		return get(size - 1);
	}

	// 8、修改链表的第 index 个位置的元素
	public void set(int index,E e){

		Node cur = dummyHead;

		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}

		cur.e = e;
	}

	// 9、查找链表中是否有该元素
	public boolean contains(E e){
		Node cur = dummyHead;

		// 从头到尾遍历所有元素,直到找到那个元素,或者遍历结束后找不到
		while (cur != null){
			if(cur.e.equals(e)){
				return  true;
			}
			cur = cur.next;
		}

		return  false;
	}

	// 10、删除链表的第 index 个位置的元素
	public E remove(int index){

		Node prev = dummyHead;

		for(int i = 0 ; i < index ; i++){
			prev = prev.next;
		}

		Node retNode = prev.next;
		prev.next = retNode.next;
		retNode.next = null;

		size--;
		return  retNode.e;
	}

	// 11、清空所有元素
	public void clear(){
		size = 0;
		dummyHead = null;
	}

	@Override
	public String toString(){
		StringBuilder res = new StringBuilder();

		Node cur = dummyHead.next;
		while(cur != null){
			res.append(cur.e + "->");
			cur = cur.next;
		}
		res.append("NULL");

		return res.toString();
	}

}

在这里插入图片描述
双向链表
单链表的基础上,每个节点额外增加了一个属性:前驱节点 prev 指针。
在这里插入图片描述

package test;

public class MyDoubleLinkedList<E> {

	private class Node{
		// 当前节点存储的值
		public  E e;

		// 当前节点存储的执行下一个节点的地址
		public  Node next;

		// 当前节点存储的执行前一个节点的地址
		public  Node prev;

		public Node(E e, Node next,Node prev){
			this.e = e;
			this.next = next;
			this.prev = prev;
		}

		public Node(E e){
			this(e, null,null);
		}

		public Node(){
			this(null, null,null);
		}
	}
	private Node dummyHead;

	// 头节点
	private Node head;
	// 尾节点
	private Node tail;
	// 元素的数量
	private int size;
	public MyDoubleLinkedList(){
		head = new Node();
		tail = new Node();
		size = 0;
	}
	// 获取链表中的元素个数
	public  int getSize(){
		return size;
	}

	// 判断链表是否为空
	public boolean isEmpty(){
		return  size == 0;
	}
	// 获取链表的第 index 个位置的元素
	public E get(int index) {

		if( index < size / 2){
			Node cur = head;
			// 这个节点是在前半部分,从头节点开始遍历
			for (int i = 0; i < index; i++) {
				cur = cur.next;
			}
			return cur.e;
		}else{
			Node cur = tail;
			// 这个节点是在后半部分,从尾节点开始遍历
			for (int i = size - 1; i > index; i--) {
				cur = cur.prev;
			}
			return cur.e;
		}
	}
	// 在双向链表头部添加元素
	public void addFirst(E e){
		// 1、新建插入节点 node
		Node node = new Node(e);
		// 2、head 前驱指向 node
		head.prev = node;
		// 3、node 后驱指向 head
		node.next = head;
		// 4、head 移动到 node 节点的位置
		head = node;
		size++;

	}
	// 在双向链表尾部添加元素
	public void addLast(E e){
		// 1、新建插入节点 node
		Node node = new Node(e);
		// 2、node 前驱指向 tail
		node.prev = tail;
		// 3、tail 后驱指向 node
		tail.next = node;
		// 4、tail 移动到 node 节点的位置
		tail = node;
		size++;

	}
	// 在链表中间添加元素
	public void add(int index ,E e){

		if(index == size){
			addLast(e);
		}else {
			// 新建插入节点 node
			Node node = new Node(e);
			// 找到插入 node 位置的前一个节点 preNode 和后一个节点 nextNode
			Node nextNode;
			if( index < size / 2){
				Node cur = head;
				// 这个节点是在前半部分,从头节点开始遍历
				for (int i = 0; i < index; i++) {
					cur = cur.next;
				}
				nextNode = cur;
			}else{
				Node cur = tail;
				// 这个节点是在后半部分,从尾节点开始遍历
				for (int i = size - 1; i > index; i--) {
					cur = cur.prev;
				}
				nextNode = cur;
			}
			Node preNode = nextNode.prev;

			// 3、 preNode 后驱指向 node
			preNode.next = node;
			// 4、nextNode 前驱指向 node
			nextNode.prev = node;
			// 5、node 前驱指向 preNode
			node.prev = preNode;
			// 6、node 后驱指向 nextNode 
			node.next = nextNode;
		}
		size++;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值