数组与单、双链表详解

一、数组

数组的定义

格式一:

元素类型[] 数组名 = new 元素类型[元素的个数或数组长度]

示例:

int[] array1 = new int[2];
array1[0]=1;
array1[1]=2;
array1[2]=3;

格式二:

元素类型[] 数组名 = new 元素类型[]{元素,元素,……};

示例:

int[] arr = new int[]{3,5,1,7};
int[] arr = {3,5,1,7};

注意:给数组分配空间时,必须指定数组能够存储的元素个数来确定数组大小。创建数组之后不能修改数组的大小。可以使用length属性获取数组的大小

数组的优缺点

优点

  • 随机访问性强(通过下标进行快速定位)
  • 查找速度快

缺点

  • 插入和删除效率低(插入和删除需要移动数据)
  • 可能浪费内存(因为是连续的,所以每次申请数组之前必须规定数组的大小,如果大小不合理,则可能会浪费内存)
  • 内存空间要求高,必须有足够的连续内存空间。
  • 数组大小固定,不能动态拓展

二、链表

普及一下
线性表分为顺序存储(顺序表)和链式存储(单链表、双链表、循环链表、静态链表,前面三种指针实现,后面是借助数组实现)

1、单链表

单链表只有一个指向下一节点的指针,也就是只能next

定义单链表

首先定义一个Node类

 public class Node {
 protected Node next; //指针域  
 public  int data;//数据域  
   
 public Node( int data) {  
       this. data = data;  
 }  
   
 //显示此节点  
 public void display() {  
      System. out.print( data + " ");  
 }  
}

接下来定义一个单链表

public class LinkList {
public Node first; // 定义一个头结点
private int pos = 0;// 节点的位置

public LinkList() {
    this.first = null;
}

// 插入一个头节点
public void addFirstNode(int data) {
    Node node = new Node(data);
    node.next = first;
    first = node;
}

// 删除一个头结点,并返回头结点
public Node deleteFirstNode() {
    Node tempNode = first;
    first = tempNode.next;
    return tempNode;
}

// 在任意位置插入节点 在index的后面插入
public void add(int index, int data) {
    Node node = new Node(data);
    Node current = first;
    Node previous = first;
    while (pos != index) {
        previous = current;
        current = current.next;
        pos++;
    }
    node.next = current;
    previous.next = node;
    pos = 0;
}

// 删除任意位置的节点
public Node deleteByPos(int index) {
    Node current = first;
    Node previous = first;
    while (pos != index) {
        pos++;
        previous = current;
        current = current.next;
    }
    if (current == first) {
        first = first.next;
    } else {
        pos = 0;
        previous.next = current.next;
    }
    return current;
}

// 根据节点的data删除节点(仅仅删除第一个)
public Node deleteByData(int data) {
    Node current = first;
    Node previous = first; // 记住上一个节点
    while (current.data != data) {
        if (current.next == null) {
            return null;
        }
        previous = current;
        current = current.next;
    }
    if (current == first) {
        first = first.next;
    } else {
        previous.next = current.next;
    }
    return current;
}

// 显示出所有的节点信息
public void displayAllNodes() {
    Node current = first;
    while (current != null) {
        current.display();
        current = current.next;
    }
    System.out.println();
}

// 根据位置查找节点信息
public Node findByPos(int index) {
    Node current = first;
    if (pos != index) {
        current = current.next;
        pos++;
    }
    return current;
}

// 根据数据查找节点信息
public Node findByData(int data) {
    Node current = first;
    while (current.data != data) {
        if (current.next == null)
            return null;
        current = current.next;
    }
    return current;
}
}

测试类

public class TestLinkList {
public static void main(String[] args) {  
    LinkList linkList = new LinkList();  
    linkList.addFirstNode(20);  
    linkList.addFirstNode(21);  
    linkList.addFirstNode(19);  
     //print19,21,20  
    linkList.add(1, 22); //print19,22,21,20  
    linkList.add(2, 23); //print19,22,23,21,20  
    linkList.add(3, 99); //print19,22,23,99,21,20  
  //调用此方法会print 19,22,23,99,21,20 
    linkList.displayAllNodes();  
    }
}

2、双链表

双链表除了有一个指向下一节点的指针外,还有一个指向前一结点的指针,可以通过prev快速找到前一结点

定义双链表

首先定义一个Node类

public class Node{
    public Node data;	//数据区
    public Node next; 	//指针区
    public Node (Node data,Node next){
        this.data = data ;
        this.next = next;
    }
    public Node(){
    }
    public void setData(Node data){
        this.data = data;
    }
    public Node getData(){
    	return data;
    }
    public void setNext(Node next){
	    this.next=next;
    }
    public Node getNext(){
    	return next;
    }
}

定义一个双链表

public class LinkList{
public  Node  head;	//头节点
public  int  count;		//记录节点的长度
public  LinkList(){	//构造函数用来初始化
	head = null;
	count = 0;
}
//节点的添加
public void addNode(Node data){
	Node node = new Node(data,null);
	Node temp = null;
	If(head!= null){
		temp = head;
		while(temp.getNext()!=null){
			Temp = temp.getNext();
		}
	temp.setNext(node);
	}else{
		head = node;
		temp = node;
	}
count++;
}
//节点的删除
public void delNode(Node data){
	
		Node  front = null;	//定义一个空节点,用于接收和判断被删除节点的前面还有没有节点
		while(head!= null){
			If(head.equals(data)){
				break;
			}
			front=head;
			head = head.getNext();
		}
		If(head!=null){
			If(front==null){
				head = head.getNext();
			}else{
				front.setNext(head.getNext());
			}
			count--;
		}else{
			Count--;
		}
	
}
//给定下标删除节点
public void delNode_count(int index){
	if(index<0||index>count-1){
		System.out.println("链表索引越界");
	}
	Node temp = head;	//作用同上
	//找到要删除节点的前一个节点
	for(int i=0;i<index-1;i++){
		temp=temp.getNext();
	}
							//找到之后 此节点的前节点和此节点的后节点进行连接
							//让要删除节点的前一个节点,指向被删除节点的后一个节点,也就是指向要删除节点的后后一个节点
	temp.setNext(temp.getNext().getNext());		//把要删除的节点隔过去进行连接,也就是实现了删除节点的操作
	//删除之后链表的长度变短了1位
	count--;
}
	//以给出的index 查找节点


public Node findNode(int index){
	if(index<0||index>count-1){
		System.out.println("链表索引越界");			
	}
	Node temp = head;
	for(int i=0;i<index-1;i++){
		temp=temp.getNext();	//找到之后获取index在链表中的位置,表示链表中第index个节点的值是temp.getData;
	}
	return temp;	//根据需要可返回找到的数据对象,也可不返回,此处建议返回,这样可以把链表封装起来
}

//以对象查找节点
public Node findNode(NodeData data){
		Node temp = head;
		while(temp!=null){
			if(temp.equals(data)){
				return temp;
			}
			temp.setNext(temp.getNext());
		}
		return null;
}
//修改
public void updateNode(NodeData data){
	Node temp = findNode(data);
	if(temp!=null){
		temp.setData(data);
	}
}
//打印
public void print(){
	Node temp=head;
	while(temp!=null){
		temp.print();
		//temp.print(temp.getData().toString());
		temp=temp.getNext();
	}
}
}

3、链表相对于数组的优缺点

时间复杂度对比

操作 数组 链表
读取 O(1) O(n)
插入 O(n) O(1)
删除 O(n) O(1)

优点

  • 插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素)
  • 内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间)
  • 大小没有固定,拓展很灵活。

缺点

  • 不能随机查找,必须从第一个开始遍历,查找效率低

4、单链表与双链表的区别

单链表只有一个指向下一结点的指针,也就是只能next

双链表除了有一个指向下一结点的指针外,还有一个指向前一结点的指针,可以通过prev()快速找到前一结点,顾名思义,单链表只能单向读取

双链表的优点

  • 删除单链表中的某个结点时,一定要得到待删除结点的前驱,得到该前驱有两种方法,第一种方法是在定位待删除结点的同时一路保存当前结点的前驱。第二种方法是在定位到待删除结点之后,重新从单链表表头开始来定位前驱。尽管通常会采用方法一。但其实这两种方法的效率是一样的,指针的总的移动操作都会有2*i次。而如果用双向链表,则不需要定位前驱结点。因此指针总的移动操作为i次。
  • 查找时也一样,我们可以借用二分法的思路,从中间节点开始前后同时查找,这样双链表的效率可以提高一倍。

单链表的优点

  • 从存储结构来看,每个双链表的节点要比单链表的节点多一个指针,而长度为n就需要 n*length(这个指针的length在32位系统中是4字节,在64位系统中是8个字节) 的空间,这在一些追求时间效率不高应用下并不适应,因为它占用空间大于单链表所占用的空间;这时设计者就会采用以时间换空间的做法,这时一种工程总体上的衡量。
原文地址:https://www.cnblogs.com/sxkgeek/p/9347071.html