1. 链表
1.1 如何实现单链表的增删操作
链表是一个基本的数据结构,存储特点是:可以用任意一个存储单元存储单链表中的数据元素(可以不连续)。单链表中的一个存储单元包括存储的数据元素和指向直接后继的索引。这两个总和成为一个单链表总的一个节点。单链表存储形式如下:
一个单链表的节点可以有如下定义:
Class Node{
Node next=null;//指向下一个节点
int data;//存储的元素
public void Node(int data){
this.data=data;
}
}
单链表中最主要的操作是插入和删除节点。
在单链表中的节点ai和ai-1之间插入一个元素的步骤如下:
1、找到ai-1的引用(存储位置)p
2、生成一个数据域为x的新节点s
3、设置p.next=s
4、设置s.next=a
在一个单链表中插入一个节点:
Class Node{
Node next=null;//指向下一个节点
int data;//存储的元素
public void Node(int data){
this.data=data;
}
}
public class LinkedList{
Node head=null;//表头的引用
public void addNode(int a){//a:插入的节点信息
Node newNode = new Node(a);
if(head==null){//当单链表为空
head=newNode;//将该节点作为头结点
return;
}
Node tmp = head;
while(tmp.next!=null){//当头结点不为空
tmp = tmp.next;
}
//将一个结点插入单链表(尾插)
tmp.next=newNode;
}
在单链表中删除第i个节点步骤如下:
1、找到ai-1的存储位置p
2、让p.next指向ai节点的直接后继节点ai+1
在单链表中删除第i个节点,如果删除成功返回true,返回失败为false:
public class LinkedList{
Node head=null;
public Boolean deleteNode(int index){//i为第i个节点
if(index<1||index>LinkedList.length()){//当删除的节点不在链表中
return false;
}
if(index==1){//当删除头结点
head = head.next;
return true;
}
int i=1;
Node preNode = head;//将头结点作为前一个节点
Node curNode = preNode.next;//前一个节点的后继为当前节点(需要删除的节点)
while(curNode!=null){//当前节点不为空
preNode.next = curNode.next//删除当前节点,当前节点的后继作为亲一个节点的后继
return true;
}
preNode = curNode;//当前节点的位置成为前一个节点
curNode = curNode.next;//当前节点的下一个节点成为当前节点
i++;//将其余的后面的节点顺次前移
}
return true;
}
}
返回链表的长度:
public int length(){
int length=0;//初始化长度
Node node = head;//链表的头结点
while(node!=null){//当节点不为空
length++;//长度加一
node = node.next;
}
return length;
}
对链表进行排序,返回排序后的头结点:
public void sortLinkedList(){
Node curNode = head;//定义两个相邻节点
Node nextNode = null;
int temp = 0;
while(curNode.next!=null){
nextNode = curNode.next;
if(nextNode<curNode){
temp = curNode;
curNode = nextNode;
nextNode = temp;
}
nextNode = nextNode.next;
}
curNode = curNode.next;
return head;//返回排序后的头结点
}
public void printList(){//对排序后的链表元素挨个打印
Node tmp = head;
while(tmp!=null){
System.out.println(tmp.data);
tmp = tmp,next;
}
}
}
1.2 如何从链表中删除重复数据
通过遍历链表,将遍历到的值存储到一个hashtable中,若当前遍历到的元素在hashtable中已经存在,则该数据是重复的,进行删除。
public void deleteDuplecate(Node head){
Node node=head;
node pre;
Hashtable<Integer,Integer> table = new Hashtable<Integer,Integer>();//创建一个hashtable存储遍历到的元素
if(node!=null){//当该节点不为空
if(table.constanisKey(node.data)){//当遍历的元素在hashtable中存在
pre.next = node.next;//则删除该节点
}
else{
table.put(node.data,1);//否则将该元素存入hashtable
node = node.next;
}
node = node.next;//否则遍历下一个节点
}
}
方法二:
public void deleteDuplecate(Node tmp){
Node node = head;
Node next;
if(node==null){//当链表为空
return null;
}
if(node!=null){//当链表不为空
while(node.next!=null){//当该节点的下一个节点不为空
next = node.next;
if(node.data==next.data){//判断node与其他节点是否相等
node.next = next.next;
}
else{
next = next.next;
}
node = node.next;
}
}
1.3 如何找出单链表中的倒数第k个元素
第一种方法是先遍历一遍链表,得出单链表的长度n,将倒数第k个元素转换为正数第n-k个,然后再遍历一次就可以得到结果。
public int getK(){
Node node = head;
int length = 0;
int n;
while(node!=null){
lenght++;
node = node.next;
}
n = length;
int k;
for(int i=0;i<n;i++){
if(i.data==(n-k).data){
System.out.println((n-k).data);
}
}
}
第二种方法,若从头到尾从链表中的某个元素开始,遍历k个元素后刚好到达链表尾,则该元素就是要找的倒数第k个元素。则有:从头结点开始,依次对链表的每一个节点元素进行测试,遍历k个元素,查看是否到达链表尾,直到找到倒数第k个元素。该方法将对同一批元素进行反复多次的遍历,对链表的大部分元素,都要遍历k个元素。
第三种方法是使用两个指针,一个指针先向前移动k-1次,接着两个指针同时向前移动,直到第一个指针的值为空时,另一个指针的位置就是所要找的位置。
public Node findK(Node head,int k){
Node n1 = null;
Node n2 = null;
if(k<1||k>this.length()){
return null;
}
int i = 0;
while(i<k-1){
n1 = n1.next;
i++;
}
while(n1!=null){
while(n2!=null){
n1 = n1.next;
n2 = n2.next;
}
}
return n2;
}
1.4 如何实现链表的反转
i,m,n是三个相邻节点,设经过若干次操作,将节点i之前的指针调整完毕,这些节点的next指针都指向前面的一个节点。当遍历到m节点,需要将m节点的next指向i,但是这样会使得m指向n的指针断开。因此,在改变m的next指向前,需要将n存储一下,再找到反转后的冷链表的头结点,反转后的链表的头结点是原始链表的尾结点。
public void ReverseIteratively(Node head){
Node node = head;
Node pre = null;
Node ReverseHead = head;
if(node==null){
return null;
}
while(node!=null){
nodeNext = node.next;
if(nodeNext==null){
ReverseHead = node;
}
pre = node;
node.next = pre;
node = nodeNext;
}
this.head = ReverseHead;
}
1.5 从尾到头输出单链表
首先第一种方法是将单链表中每个节点的连接指针反过来,这样等于是将整个单链表反转,然后从头到尾一次输出每个元素。
public void ReverseIteratively(Node head){
Node node = head;
Node pre = null;
Node ReverseHead = head;
if(node==null){
return null;
}
while(node!=null){
nodeNext = node.next;
if(nodeNext==null){
ReverseHead = node;
}
pre = node;
node.next = pre;
node = nodeNext;
}
this.head = ReverseHead;
}
public Node print(Node head){
for(int i=0;i<length;i++){
System.out.println(i);
}
}
第二种方法是从头到尾遍历链表,每经过一个元素,将该元素放入栈中。当遍历完整个链表,从栈的顶部输出栈中的元素。这样就将链表中的值从尾到头输出出来。
public int printLinkedListReversely(){
Node node = head;
Stack<Integer> stack = new Stack<Integer>();
if(node==null){
return null;
}
while(node!=null){
stack.push(node.data);
node = node.next;
}
for(int i=0;i<stack.length;i++){
System.out.println(i);
}
}
使用递归进行操作,每访问一个节点,先递归输出其后的节点,再输出其本身节点。
public int printLinkedListReversely(Node head){
if(head.next!=null){
printLinkedListReversely(head.next);
System.out.println(head.data);
}
}
1.6 如何寻找单链表中间节点
第一种方法是首先遍历单链表,得到单链表的长度n,然后遍历n/2,得到单链表的中间节点。该方法需要遍历两次,第一次遍历整个单链表,第二次遍历单链表的一半的长度。
public Node middleNode(){
int length=0;
Node node = head;
if(node==null){
return null;
}
while(node!=null){
length++;
node = node.next;
}
int n;
n = length/2;
for(int i=0;i<n;i++){
if(i.data==n.data){
System.out.println(i);
}
}
}
如果是双向链表,使用两个方向的指针同时进行遍历,一个从头到尾进行遍历,一个从尾到头遍历,当两个指针相遇时,则得到中间元素。
在单链表中也可以使用双指针实现中间节点的快速查找。第一步:两个指针同时从头到尾开始遍历;第二步:一个指针一次移动两步,一个指针一次移动一步;第三步:快指针先到链表尾部,慢指针到达链表中部。
public Node middleNode(Node head){
Node n1 = this.head;
Node n2 = this.head;
while(n1!=null&&n1.next!=null&&n1.next.next!=null){
n1 = n1.next.next;
n2 = n2.next;
}
return n2;
}
1.7 如何检测一个链表是否有环
使用两个指针,一个是快指针fast,一个是慢指针slow,二者的初始值都指向链表投,slow每次移动一步,fast每次移动两步,两个指针同时同向移动,快指针每移动一次和慢指针比一次,直到快指针等于慢指针为止,说明这个链表是带有环的单向链表。否则为不带环的单链表。
public boolean IsLoop(Node head){
Node f = head;//快指针
Node s = head;//慢指针
if(f==null){
return false;
}
if(f!=null&&f.next!=null){
f = f.next.next;
s = s.next;
while(f==s){
return true;
}
}
return !(f==null||f.next==null);
}
1.8 如何在不知道头指针的情况下删除指定节点
分为两种情况讨论:
1、当待删除的节点时链表尾结点,无法删除,因为删除后无法使得其前驱结点的next指针为空。
2、当待删除的节点不是尾结点,可以通过交换该节点和其后继节点的值,然后删除后继节点。
public boolean deleteNode(Node head){
Node n = head;
int temp ;
if(n==null&&n.next==null){
return false;
}
tmp = n.data;
n.data = n.next.data;
n.next.data = tmp;
n = n.next.next;
return true;
}
1.9 如何判断两个链表是否相交
如果两个链表相交,则他们一定有相同的尾结点,所以分别遍历连个链表,记录他们的尾结点,如果尾结点相同,则这两个链表相交,否则不相交。
public boolean isIntersect(Node head1,Node head2){
Node n1 = head;
if(n1==null||n2==null){
return false;
}
//判断两个链表中的
while(n1.next!=null){
n1 = n1.next;
}
while(n2.next!=null){
n2 = n2.next;
}
if(n1==n2){
return true;
}
}
1.10 使用头插法建立单链表
从空表开始,生成新节点,将读取到的数据存放到新节点的数据域中,将新节点插入到当前链表的表头,即头结点之后。
实现算法的伪代码如下:
头插法建立单链表时,读取数据的顺序与生成的俩表中元素的顺序是相反的。
1.11 按序号查找结点值
在单链表中从第i个结点出发 ,顺指针 next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域 NULL。
Class Node{
Node next = null;
int data;
public void Node(int data){
this.data = data;
}
}
public class Find{
int n=0;
public int length(Node head){
Node node = head;
while(node!=null){
n++;
node = node.next;
}
return n;
}
public int find(Node head, int a){
Node node = head;
if(node==null){
return false;
}
while(node!=null){
for(int i=0;i<n;i++){
if(i.data == a){
double p = i.data;
return p;
}
else{
break;
}
}
}
}
1.12 按照值查找表结点
从单链表第一个节点开始,从前往后一次比较表中各节点的值,当某节点的值等于给定值,返回该节点的指针,当单链表中的节点值都不等于给定值则返回null。
public class Find{
int n=0;
public int length(Node head){//计算链表长度
while(node!=null){
n++;
node = node.next;
}
return n;
}
public void find(int a,Node head){
Node node = head;
if(data==null){
return false;
}
while(data!=null){
for(int i=0;i<n;i++){
if(i.data==a){
return i;
}
else{
break;
}
}
}
}
}