一、链表介绍
链表结构如上所示(图画的不好请原谅),链表中存放着一个个Node,而每个Node又分为两部分,前一部分存放着Data,而后一部分存放着下一个节点的引用,这样一层层引用下去的一种结构。所以链表的结构决定了它的存储并不需要连续的存储空间。这样的链表访问头数据比较快,另外删除也比较快,但是查找靠后的数据稍微慢了点。
二、自定义链表的实现
1. 节点类的实现
因为链表是由节点组成的,所以先实现节点
public class Node {
Node next=null;
public int data;
public Node(int data) {
this.data = data;
}
}
节点类很简单,两部分,第一部分存放着数据(这里数据用的int,也可以自定义其他类型),第二部分用来存放下一个节点的引用,我们暂时定义为null。
2. 链表类的实现
public class MyLinkedList {
public Node head=null;
}
现在的链表类很简单,只有一个head,因为我们链表一般对外只提供头结点,所以只需要一个head就足够。下面介绍链表方法实现。
2.1 得到链表的长度
/**
* @return 返回此链表长度
*/
public int length(){
int length=0;
Node temp=head;
while(temp!=null){
temp=temp.next;
length++;
}
return length;
}
上面代码返回链表长度
2.2 为链表增加节点
2.2.1 尾部添加新节点
如图,在尾部添加节点,只需要把目前最后一个链表的next指向新节点就OK啦。
/**
* 向链表结尾中添加数据
* @param d 插入的数据
*/
public void addNode(int d){
Node newNode=new Node(d);
if(head==null){//如果此链表中没有数据。
head=newNode;
return;
}
Node temp=head;
while(temp.next!=null){
temp=temp.next;
}
temp.next=newNode;
}
上面方法实现了向链表中增加节点,如果此链表是一个空链表,这直接给链表中的head赋值,即完成了添加新节点任务,否则我我这里定义了向链表尾部添加新节点,其实可以定义向头结点添加新节点,那样效率更高。下面代码就是向头结点添加数据实现:
2.2.2 在头部添加新节点
如上图所示,在头部添加新节点,只需要把新节点的next指向原来的head节点,然后再把新节点赋值给head就搞定啦。
/**
* 向头部添加节点
* @param d
*/
public void addNodeAtFirst(int d){
Node newNode=new Node(d);
newNode.next=head;
head=newNode;
}
2.2.3 其实还可以向任意位置添加节点:
在index位置添加新节点,需要先把index-1位置的next指向新节点,再把新节点的next指向原来在index位置的节点
/**
* 向链表中添加数据
* @param data 插入的数据
* @param index 插入数据的位置 如果是向头部添加数据,这直接调用addNodeAtFirst,否则需要判断0<index<length()
* @return boolean 成功返回true 否则返回false
*/
public boolean addNode(int data,int index){
if(index==0){//向头部添加数据
addNodeAtFirst(data);
return true;
}
int length=length();
if(index<1||index>=length) return false;//index不符合规则
Node newNode=new Node(data);
int i=1;
Node temp=head;
while(temp.next!=null){
if(i==index){
newNode.next=temp.next;
temp.next=newNode;
return true;
}else {
i++;
temp=temp.next;
}
}
return false;
}
2.3 删除链表中的数据
2.3.1 删除头结点中数据
如上图所示,删除头结点只需要把链表中的head节点转移到head节点的下一个节点。
如:head=head.next;
2.3.2 删除末节点
删除末尾节点只需要把为节点的上一个节点的next赋值为null就可以了。
2.3.3删除其他节点
删除其他节点如图,把要删除的index节点的上一个节点的next直接赋值给index节点下一个节点。言语表达有点晦涩难懂,但是大家看图片还是比较容易弄明白的哈。
下面上代码,删除尾节点和头结点的代码就不单独列出了。下面其实都包括了
/**
* 删除指定位置的链表元素 0<=index<length
* @param index 要被删除的链表
* @return 删除成功返回true 否则返回false
*/
public boolean deleteNode(int index){
int length=length();
if(index<0||index>=length) return false;//未知输入错误
if(index==0){//删除第一个元素
head=head.next;
return true;
}
Node preNode=head;
Node curNode=head.next;
int i=1;
while (curNode!=null){
if(index==i){
preNode.next=curNode.next;
return true;
}else{
i++;
}
preNode=curNode;
curNode=curNode.next;
}
return false;
}
这里不用担心存在内存泄漏,因为当你把要删除节点的对外引用去掉了以后,这个节点会自动呗gc删除,所以没有内存泄漏哦。
2.4 对链表排序
/**
* 对链表进行排序(按照数据data从小到大排序,这里的data是int类型) 运用了插入排序算法
* @return 返回排序后链表的头点
*/
public Node orderList(){
Node nextNode=null;
Node curNode=head;
while(curNode!=null){
nextNode=curNode.next;
while(nextNode!=null){
if(nextNode.data<curNode.data){//data数据类型交换
curNode.data=curNode.data-nextNode.data;
nextNode.data=curNode.data+nextNode.data;
curNode.data=nextNode.data-curNode.data;
}
nextNode=nextNode.next;
}
curNode=curNode.next;
}
return head;
}
上面代码实现了对链表的排序方法,用的是插入排序算法:
每一个元素分别和第一个元素比较大小,小的和第一个元素交换位置.第一次循环后,最小的就在最前面了。
然后从第二各元素开始。第二个元素和第二个后面元素比较大小,小的放在第二个元素后面。
。。。
3. 测试结果
自定义简单链表测试了一下,算是达到预期吧。如果有错误请指正。谢谢
package com.example.yang.mylinkedlistdemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView text;
private MyLinkedList list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text=(TextView)findViewById(R.id.text);
list=new MyLinkedList();
list.addNode(4);
list.addNode(2);
list.addNode(1);
list.addNodeAtFirst(999);
list.addNode(998,1);
list.addNode(100);
list.addNode(55);
text.setText("");
pritlnList();
list.deleteNode(2);
pritlnList();
list.orderList();
pritlnList();
}
private void pritlnList() {
Node tmp=list.head;
while (tmp!=null){
text.append(tmp.data+",");
tmp=tmp.next;
}
text.append("\n");
}
}
用的Android Studio测试的
下面是结果图片:
三、链表中的其他方法实现
1. 删除链表中的重复数据
1.1 时间优先
/**
* 时间复杂度低
* 删除重复
*/
public void deleteDuplecateTime(){
Hashtable<Integer,Integer> table=new Hashtable<>();
Node temp=head;
Node pre=null;
while(temp!=null){
if(table.containsKey(temp.data)){
pre.next=temp.next;
}else {
table.put(temp.data,1);
pre=temp;
}
temp=temp.next;
}
}
实现方法是遍历值存储在一个Hashtable中,遍历中查询Hahstable中是否存在此元素,存在就删除。利用了Hashtable中的Key不能重复这一特点。
1.2 空间优先
/**
* 空间复杂度低
* 删除重复
*/
public void deleteDuplecateSpace() {
Node curNode = head;
Node nextNode = null;
while (curNode != null) {
nextNode = curNode;
while (nextNode.next != null) {
if (curNode.data == nextNode.next.data) {
nextNode.next = nextNode.next.next;
}
nextNode = nextNode.next;
}
curNode=curNode.next;
}
}
思路类似与插入排序算法,两层循环,第一层正常遍历,第二层从第一层的开始位置遍历,然后每个都和第一层的比较,如果数据相同,就删除。
优点为空间占用小,确定为时间占用会比第一种方法大一些。
2. 找出单链表中倒数第K个元素
先说一下算法思路,为了只遍历一次就能得到倒数第K个元素:
设置两个指针,第一个指针比第二个指针快k-1步,然后两个指针一起向后遍历
当第一个指针到尾节点(尾节点的next=null)时,第二个指针的位置就是倒数第k个元素的位置
/**
* 找到倒数第k个元素
* @param k 0<k<=length
* @return 找到的节点
*/
public Node findElemAtLast(int k){
if(k<1) return null;
Node p1=head;
Node p2=head;
if(p1==null) return null;
for(int i=0;i<k-1;i++){
p1=p1.next;
if(p1==null) return null;
}
while(p1.next!=null){
p1=p1.next;
p2=p2.next;
}
return p2;
}