单链表
用一组地址任意的存储单元存放线性表中的数据元素。
数据域 (数据元素)+ 指针域 (指示后继元素存储位置)= 结点
单链表创建与输出
package danlianbiao;
public class LinkList {
public Node head;//头结点
public Node current;//当前结点
//方法:向链表中添加数据
public void add(int data) {
//判断链表为空的时候
if (head == null) {//如果头结点为空,说明这个链表还没有创建,那就把新的结点赋给头结点
head = new Node(data);
current = head;
} else {
//创建新的结点,放在当前节点的后面(把新的结点合链表进行关联)
current.next = new Node(data);
//把链表的当前索引向后移动一位
current = current.next; //此步操作完成之后,current结点指向新添加的那个结点
}
}
//方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历
public void print(Node node) {
if (node == null) {
return;
}
current = node;
while (current != null) {
System.out.println(current.data);
current = current.next;//指向下一个结点;
}
}
class Node {
//注:此处的两个成员变量权限不能为private,因为private的权限是仅对本类访问。
int data; //数据域
Node next;//指针域
public Node(int data) {
this.data = data;
}
}
public static void main(String[] args) {
LinkList list = new LinkList();
//向LinkList中添加数据
for (int i = 0; i <20; i++) {
list.add(i);
}
list.print(list.head);// 从head节点开始遍历输出
int a=list.getLength(list.head);
}
}
获取单链表长度
//方法:获取单链表的长度
public int getLength(Node head) {
if (head == null) {
return 0;
}
int length = 0;
Node current = head;
while (current != null) {
length++;
current = current.next;
}
return length;
}
查找单链表中倒数第k个结点
这里我们可以有几种思路,一种是遍历全部,得出链表长度,然后直接输出第size-k个结点,但是这个不够全面,我们要考虑到k=0和k超出链表长度的情况,所以我们可以声明两个结点型的变量first和second,首先让first和second都指向第一个结点,然后让second往后移动k-1个位置,再让整体向后移,直到second移到最后一个结点,这时first就是所要找的结点
public Node findLastNode(Node head, int k) {
if (k == 0 || head == null) {
return null;
}
Node first = head;
Node second = head;
//让second结点往后挪k-1个位置
for (int i = 0; i < k - 1; i++) {
System.out.println("i的值是" + i);
second = second.next;
if (second == null) { //说明k的值已经大于链表的长度了
//throw new NullPointerException("链表的长度小于" + k); //我们自己抛出异常,给用户以提示
return null;
}
}
//让first和second结点整体向后移动,直到second走到最后一个结点
while (second.next != null) {
first = first.next;
second = second.next;
}
//当second结点走到最后一个节点的时候,此时first指向的结点就是我们要找的结点
return first;
}
查找单链表中的中间结点
算法与上述类似,只不过这次second移两步,first移一步;直到second移到最后一个结点处;
//方法:查找链表的中间结点
public Node findMidNode(Node head) {
if (head == null) {
return null;
}
Node first = head;
Node second = head;
//每次移动时,让second结点移动两位,first结点移动一位
while (second != null && second.next != null) {
first = first.next;
second = second.next.next;
}
//直到second结点移动到最后一个结点时,此时first指针指向的位置就是中间结点的位置
return first;
}
合并两个有序的单链表,合并之后的链表依然有序
//两个参数代表的是两个链表的头结点
public Node mergeLinkList(Node head1, Node head2) {
/*
* 首先判断两个合并的链表是否为空,或者其中一个为空
* 然后新链表的头结点和当前结点初始为空,
* 依次扫描head1和head2的值,比较当前元素的值,将小的放进新链表中,
* 直到其中一个链表为空,再将另一个链表剩余存放进新链表
*/
if (head1 == null && head2 == null) { //如果两个链表都为空
return null;
}
if (head1 == null) {
return head2;
}
if (head2 == null) {
return head1;
}
Node head; //新链表的头结点
Node current; //current结点指向新链表
// 一开始,我们让current结点指向head1和head2中较小的数据,得到head结点
if (head1.data < head2.data) {
head = head1;
current = head1;
head1 = head1.next;
} else {
head = head2;
current = head2;
head2 = head2.next;
}
while (head1 != null && head2 != null) {
if (head1.data < head2.data) {
current.next = head1; //新链表中,current指针的下一个结点对应较小的那个数据
current = current.next; //current指针下移
head1 = head1.next;
} else {
current.next = head2;
current = current.next;
head2 = head2.next;
}
}
//合并剩余的元素
while (head1 != null) { //说明链表2遍历完了,是空的
current.next = head1;
head1=head1.next;
current=current.next;
}
while (head2 != null) { //说明链表1遍历完了,是空的
current.next = head2;
head2=head2.next;
current=current.next;
}
return head;
}
测试:
LinkList list1 = new LinkList();
LinkList list2 = new LinkList();
//向LinkList中添加数据
for (int i = 0; i < 4; i++) {
list1.add(i);
}
for (int i = 5; i < 8; i++) {
list2.add(i);
}
LinkList list3 = new LinkList();
list3.head = list3.mergeLinkList(list1.head, list2.head); //将list1和list2合并,存放到list3中
list3.print(list3.head);// 从head节点开始遍历输出
单链表的反转
//方法:链表的反转
public Node reverseList(Node head) {
//如果链表为空或者只有一个节点,无需反转,直接返回原链表的头结点
if (head == null || head.next == null) {
return head;
}
Node current = head;
Node next = null; //定义当前结点的下一个结点
Node reverseHead = null; //反转后新链表的表头
while (current != null) {
next = current.next; //暂时保存住当前结点的下一个结点,因为下一次要用
current.next = reverseHead; //将current的下一个结点指向新链表的头结点
reverseHead = current;
current = next; // 操作结束后,current节点后移
}
return reverseHead;
}
从尾到头打印单链表
public void reversePrint(Node head) {
if (head == null) {
return;
}
reversePrint(head.next);
System.out.println(head.data);
}
判断单链表是否有环
//方法:判断单链表是否有环
public boolean hasCycle(Node head) {
if (head == null) {
return false;
}
Node first = head;
Node second = head;
while (second != null) {
first = first.next; //first指针走一步
second = second.next.next; second指针走两步
if (first == second) { //一旦两个指针相遇,说明链表是有环的
return true;
}
}
return false;
}
删除链表中的重复结点
/**
* 删除重复节点
*/
public void deleteDuplecate(Node head) {
Node p = head;
while (p != null) {
Node q = p;
while (q.next != null) {
if (p.data == q.next.data) {
q.next = q.next.next;
} else
q = q.next;
}
p = p.next;
}
}
删除链表中指定值的结点
public Node removeElements(Node head,int data)
{
/*
* 这里要判断头结点是否为要删除的值,
* 判断完后,要有一个当前结点和当前结点的前一个结点
* 当前结点遍历,遇到指定值,使pre.next=current.next,完成删除,没遇到就是pre=pre.next;
* 接着current=current.next移到下一个结点,最后返回头结点
*/
while(head!=null)
{
if(head.data!=data)
{
break;
}
head=head.next;
}
Node current=head;
Node pre=head;
while(current!=null)
{
if(current.data==data)
{
pre.next=current.next;
}
else {
pre=current;
}
current=current.next ;
}
return head;
}