转载声明:文章来源:https://blog.csdn.net/yongwan5637/article/details/88353348
数组与链表的区别
数组是具有相同的数据类型且按一定次序排列的一组变量的集合体,数组在内存中的地址是连续的(链表内存地址是散列、不连续的)。数组是一种引用数据类型,数组元素类似对象的成员变量。链表通过指针连接元素,分为单向链表、双向链表和循环链表。数组静态分配内存,链表动态分配内存。数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
数组的优点
随机访问性强(通过下标进行快速定位)
查找速度快
数组的缺点
插入和删除效率低(插入和删除需要移动数据)
可能浪费内存(因为是连续的,所以每次申请数组之前必须规定数组的大小,如果大小不合理,则可能会浪费内存)
内存空间要求高,必须有足够的连续内存空间。
数组大小固定,不能动态拓展
链表的优点
插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素)
内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间)大小没有固定,拓展很灵活。
链表的缺点
不能随机查找,必须从第一个开始遍历,查找效率低
单向链表(Single-Linked List)
单链表是链表中结构最简单的。一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。
①、单向链表的具体实现
package com.ys.datastructure;
public class SingleLinkedList {
private int size;//链表节点的个数
private Node head;//头节点
public SingleLinkedList(){
size = 0;
head = null;
}
//链表的每个节点类
private class Node{
private Object data;//每个节点的数据
private Node next;//每个节点指向下一个节点的连接
public Node(Object data){
this.data = data;
}
}
//在链表头添加元素
public Object addHead(Object obj){
Node newHead = new Node(obj);
if(size == 0){
head = newHead;
}else{
newHead.next = head;
head = newHead;
}
size++;
return obj;
}
//在链表头删除元素
public Object deleteHead(){
Object obj = head.data;
head = head.next;
size--;
return obj;
}
//查找指定元素,找到了返回节点Node,找不到返回null
public Node find(Object obj){
Node current = head;
int tempSize = size;
while(tempSize > 0){
if(obj.equals(current.data)){
return current;
}else{
current = current.next;
}
tempSize--;
}
return null;
}
//删除指定的元素,删除成功返回true
public boolean delete(Object value){
if(size == 0){
return false;
}
Node current = head;
Node previous = head;
while(current.data != value){
if(current.next == null){
return false;
}else{
previous = current;
current = current.next;
}
}
//如果删除的节点是第一个节点
if(current == head){
head = current.next;
size--;
}else{//删除的节点不是第一个节点
previous.next = current.next;
size--;
}
return true;
}
//判断链表是否为空
public boolean isEmpty(){
return (size == 0);
}
//显示节点信息
public void display(){
if(size >0){
Node node = head;
int tempSize = size;
if(tempSize == 1){//当前链表只有一个节点
System.out.println("["+node.data+"]");
return;
}
while(tempSize>0){
if(node.equals(head)){
System.out.print("["+node.data+"->");
}else if(node.next == null){
System.out.print(node.data+"]");
}else{
System.out.print(node.data+"->");
}
node = node.next;
tempSize--;
}
System.out.println();
}else{//如果链表一个节点都没有,直接打印[]
System.out.println("[]");
}
}
}
@Test
public void testSingleLinkedList(){
SingleLinkedList singleList = new SingleLinkedList();
singleList.addHead("A");
singleList.addHead("B");
singleList.addHead("C");
singleList.addHead("D");
//打印当前链表信息
singleList.display();
//删除C
singleList.delete("C");
singleList.display();
//查找B
System.out.println(singleList.find("B"));
}
②、用单向链表实现栈
栈的pop()方法和push()方法,对应于链表的在头部删除元素deleteHead()以及在头部增加元素addHead()。
package com.ys.datastructure;
public class StackSingleLink {
private SingleLinkedList link;
public StackSingleLink(){
link = new SingleLinkedList();
}
//添加元素
public void push(Object obj){
link.addHead(obj);
}
//移除栈顶元素
public Object pop(){
Object obj = link.deleteHead();
return obj;
}
//判断是否为空
public boolean isEmpty(){
return link.isEmpty();
}
//打印栈内元素信息
public void display(){
link.display();
}
}
单链表与双链表的结构图如下:
从以上结构可以得出双链表具有以下优点:
1、删除单链表中的某个结点时,如果是尾节点一定要得到待删除结点的前驱,得到该前驱有两种方法,第一种方法是在定位待删除结点的同时一路保存当前结点的前驱。第二种方法是在定位到待删除结点之后,重新从单链表表头开始来定位前驱。尽管通常会采用方法一。但其实这两种方法的效率是一样的,指针的总的移动操作都会有2*i次。而如果用双向链表,则不需要定位前驱结点。因此指针总的移动操作为i次。
2、查找时也一样,我们可以借用二分法的思路,从head(首节点)向后查找操作和last(尾节点)向前查找操作同步进行,这样双链表的效率可以提高一倍。
可是为什么市场上单链表的使用多于双链表呢?
从存储结构来看,每个双链表的节点要比单链表的节点多一个指针,而长度为n就需要 n*length(这个指针的length在32位系统中是4字节,在64位系统中是8个字节) 的空间,这在一些追求时间效率不高应用下并不适应,因为它占用空间大于单链表所占用的空间;这时设计者就会采用以时间换空间的做法,这时一种工程总体上的衡量。
单向链表删除节点时一定需要知道前驱结点吗?
删除一个节点时,不需要知道前置节点。举例:假如链表A->B->C->D,假如要删除节点B,首先交换B和C的data,然后B->next=C->next,删除C即可,这样做的时间复杂度O(1),适用于非最后一个节点。如果是删除最后一个节点,那还是需要遍历,复杂度O(n),综合起来平均复杂度((n-1)O(1)+O(n))/n=O(1)。
深入浅出