一、单链表是一种链式存取的数据结构。是由若干个结点组成的,每个结点由:元素(数据元素的映象) + 指针(指示后继元素存储位置)两部分组成。需要注意的是结点的地址是随机给定的。
在逻辑上可以理解为这样的:
上图是不带头结点的单链表
单链表的一个重要特征就是能从前驱结点找到后继结点。而无法从后继结点找前驱结点。因为这种最普通的单链表只有一个next引用(我习惯叫指针)指向后继结点。
二、当然你也可以带头结点的那种单链表,只是将上图的第一个结点当做头结点,一般什么都不装入或者是装入一些链表信息例如:链表长度等等。
三、我写的是不带头结点的,其实区别也不是太大。主要是能理解其主要的逻辑思维就行。
其结点的结构为:
package cn.liu.Link_list;
//定义一个单链表结点
public class Node {
public Object data; //数据域
public Node next; //next引用,相当于指针。注意它的类型为Node
//为了方便,建了一个构造器不影响,可以没有。
public Node(Object data) {
super();
this.data = data;
this.next = null;
}
}
接下来是链表的一些操作:
1.建立一个单链表
这里有头插法和尾插法两种方法去建立一个链表,初始化一个空白链表。
头插法是往第一个结点的前面的插,形成一个链表。
public class Link_list {
Node head = null;//头指针永远指向首地址
public int size = 0;//记录结点的个数
//增加了一个size是为了掌控单链表的结点个数,没有什么影响。
}
//头插法建立一个单链表
public void initlist_head(int i) {
detection1(i);//这是对索引i的合格的检测,后面会贴整体代码。
for(int j = 0; j<i ; j++) {
Node newnode = new Node(null);//新建一个新的结点
if(j==0) {
head = newnode; //没有建立时,newnode便是第一个结点
}else {
newnode.next = head;//插在头结点的前面
head = newnode;//头指针始终在首结点
}
}
size = i;
}
尾插法是每次往最后一个结点加入,最后形成一个链表。
//尾插法建立一个单链表
public void initlist_tail(int i) {
detection1(1);//对索引i的合格检测
Node tail = head;//永远指向最后一个结点
for(int j = 0; j < i; j++) {
Node newnode = new Node(null);
if(j==0) {
head = newnode;
}else {
tail.next = newnode;
}
tail = newnode;
}
size = i;
}
2.插入到链表的指定位置
//插入到指定位置
public void addsomewhere(Object e,int i) {
detection2(i);//索引是否合格检测
Node newnode = new Node(e);
Node temp = head;//相当于遍历指针
Node p = temp;//指向temp后面的结点
if(i==1) {
newnode.next = head;//插在头结点的前面
head = newnode;//头指针始终在首结点
}else{
for(int j = 2; j < i;j++) {
temp = temp.next; //指向下一个结点
}
p = temp.next; //
temp.next = newnode;
newnode.next = p;
}
size++;
}
图中s就是代码中newnode,图中的p就是代码中的temp。
所以当插到首和尾的时候就是此种情况的特殊情况。
//插入到首部
public void addhead(Object e) {
addsomewhere(e, 1);
}
//插入到尾部
public void addtail(Object e) {//假设有4个结点,向尾部插一个,旧相当于在第5个位置插入
addsomewhere(e,size+1);
}
3. 获取某个位置的结点元素,得到其数据域。
//查找某个结点
private Node getNode(int i) {
if(i<=0 || i > size) {
throw new RuntimeException("索引越界异常:"+i);
}
Node temp = head;
for(int j = 1; j < i ;j++) {
temp = temp.next;
}
return temp;
}
//查找某个位置结点元素数据域
public Object get(int i) {
return getNode(i).data;
}
4.删除某个结点。
就是先找到要删除的结点前一个结点:a,再确认删除结点:b即:a.next,同时也确认删除结点的后一个结点:c即:a.next.next,
最后直接将a的next指向c就是,如图所示,即:a.next = c; 或是: a.next = a.next.next;结点b垃圾结点会自动回收,不用管。
//删除某个位置结点
public void remove(int i) {
detection2(i);
Node temp= head;
if(i == 1) {
head = head.next;
temp.next = null;
}else {
temp = getNode(i-1);//找到i的前一个
temp.next = temp.next.next;//第i-1个结点指向第i+1个结点。
}
size--;
}
5.修改某个结点的数据域。
就是先找到此结点,然后直接给数据域重新赋值即可。
//修改某个位置结点数据域
public void change(Object e,int i) {
detection2(i);//索引合格检测
getNode(i).data = e;
}
6.修改toString()方法来打印单链表。
//改写toString()打印出单链表
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
Node temp = head;
while(temp!=null){
sb.append(temp.data + "-->");
temp = temp.next;
}
sb.deleteCharAt(sb.length()-1); //删除最后一个字符
sb.deleteCharAt(sb.length()-1);
sb.setCharAt(sb.length()-1,'}');//替换最后一个字符
return sb.toString();
}
四、沾一个小测试用例。
package cn.liu.Link_list;
public class Test_Linklist {
public static void main(String[] args) {
Link_list list = new Link_list();
list.addhead("第四个结点");
list.addhead("第三个结点");
list.addhead("第二个结点");
list.addhead("第一个结点");
list.addtail("第1个结点");
list.addtail("第2个结点");
list.addtail("第3个结点");
list.addtail("第4个结点");
System.out.println(list);
}
}
五、粘上我的代码。
package cn.liu.Link_list;
//定义一个单链表结点
public class Node {
public Object data; //数据域
public Node next; //next引用,相当于指针。注意它的类型为Node
public Node(Object data) {
super();
this.data = data;
this.next = null;
}
}
package cn.liu.Link_list;
/**
* 实现单链表
* @author Administrator
*
*/
public class Link_list {
Node head = null;//头指针永远指向首地址
public int size = 0;//记录结点的个数
//头插法建立一个单链表
public void initlist_head(int i) {
detection1(i);
for(int j = 0; j<i ; j++) {
Node newnode = new Node(null);
if(j==0) {
head = newnode;
}else {
newnode.next = head;//插在头结点的前面
head = newnode;//头指针始终在首结点
}
}
size = i;
}
//尾插法建立一个单链表
public void initlist_tail(int i) {
detection1(1);
Node tail = head;//永远指向最后一个结点
for(int j = 0; j < i; j++) {
Node newnode = new Node(null);
if(j==0) {
head = newnode;
}else {
tail.next = newnode;
}
tail = newnode;
}
size = i;
}
//插入到指定位置
public void addsomewhere(Object e,int i) {
detection2(i);//索引是否合格检测
Node newnode = new Node(e);
Node temp = head;//相当于遍历指针
Node p = temp;//指向temp后面的结点
if(i==1) {
newnode.next = head;//插在头结点的前面
head = newnode;//头指针始终在首结点
}else{
for(int j = 2; j < i;j++) {
temp = temp.next;
}
p = temp.next;
temp.next = newnode;
newnode.next = p;
}
size++;
}
//插入到首部
public void addhead(Object e) {
addsomewhere(e, 1);
}
//插入到尾部
public void addtail(Object e) {//假设有4个结点,向尾部插一个,旧相当于在第5个位置插入
addsomewhere(e,size+1);
}
//查找某个位置结点元素数据域
public Object get(int i) {
return getNode(i).data;
}
//删除某个位置结点
public void remove(int i) {
detection2(i);
Node temp= head;
if(i == 1) {
head = head.next;
temp.next = null;
}else {
temp = getNode(i-1);//找到i的前一个
temp.next = temp.next.next;//第i-1个结点指向第i+1个结点。
}
size--;
}
//修改某个位置结点数据域
public void change(Object e,int i) {
detection2(i);
getNode(i).data = e;
}
//返回单链表的长度
public int length() {
return size;
}
//改写toString()打印出单链表
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
Node temp = head;
while(temp!=null){
sb.append(temp.data + "-->");
temp = temp.next;
}
sb.deleteCharAt(sb.length()-1); //删除最后一个字符
sb.deleteCharAt(sb.length()-1);
sb.setCharAt(sb.length()-1,'}');//替换最后一个字符
return sb.toString();
}
//查找某个结点
private Node getNode(int i) {
if(i<=0 || i > size) {
throw new RuntimeException("索引越界异常:"+i);
}
Node temp = head;
for(int j = 1; j < i ;j++) {
temp = temp.next;
}
return temp;
}
//检测索引是否合格
private void detection1(int i) {
if(i<=0) {
throw new RuntimeException("索引不能小于或等于0:"+i);
}
}
private void detection2(int i) {
if(i<=0 || (size>0 && i>size+1)) {
throw new RuntimeException("索引越界异常:"+i);
}
}
}