代码改变世界

数据结构之单链表一

2011-05-07 23:32  Wang_top  阅读(342)  评论(0)    收藏  举报

1:单链表实现:

下面简单的代码,就是链表中的一个节点,其实对于一个链表,我们可以理解为是一个首节点。从这个头结点可以遍历到链表中的每一项。当然对于双链表是首尾都可以遍历的。我们先讨论单链表:

链表定义 
public class Link
{
public Link Next;
public string Data;
public Link(Link next, string data)
{
this.Next = next;
this.Data = data;
}
}

2:单链表的数据遍历输出

如果要输出这个链表中的所有元素,使用如下方式:(顺序打印)

顺序打印链表 
static void Print(Link link)
{
Console.Write(link.Data
+" ");
if (link.Next!=null)
{
Print(link.Next);
}
}

如何逆序打印一个链表的元素?其实当初我在面试中也遇到过这个问题,当时一门心思的反转链表,后来经提醒才发现根本没有这个必要:

逆序打印链表 
public void Nprint(Link node)
{
if (node.next!=null)
{
Nprint(node.next);
}
Console.Write(node.value
+" ");
}

3:单链表的反转

既然上面提到了反转链表,我们就来说说如何反转一个链表,反转链表说白了就是用一个指针不断指向没有反转的部分的首地址,然后反转当前第一个未反转的元素的时候,标记下一个元素,然后将当前元素的next设置为已反转部分的首节点,最后再将首节点设置为当前节点。依次类推:

如图:

1:node为我们输入的链表首节点,所以我们遍历逆序的时候,不改变node节点的值,所以它永远指向1这个节点,同时标记head指向1,temp指向2,flag指向3.         

head =node,temp=node.next, flag=temp.next.

 

head temp flag
| | |
1---->2---->3--->4
|
node

2:设置temp.next =head,也就是说将当前元素到下一个元素的链接关系斩断,然后将它添加到已反转部分的链首。设置node.next = flag;这样我们就将下面这个反转节点2和节点1的操作完成了,然后反转后的链表头应该是节点2,也就是temp,所以需要设置head = temp;

 

|----
v
|
1---->2-*->3--->4
| ^
|----------|

反转完成这个节点后链表结构如下

 

Head temp flag
| | |
2---->1---->3--->4
|
node

下面看代码:

反转链表---循环
static Link ReverseLink(Link node)
{
Link head
= node;
Link temp
= null;
Link flag
= null;
//如果链表为空或者只有一个元素 翻转链表后依然返回link
if (node == null || node.Next == null)
return node;
while (node.Next != null)
{
temp
= node.Next;
flag
= temp.Next;
temp.Next
= head;
node.Next
= flag;
head
= temp;
}
return head;
}

 

上面例子中我们使用循环反转整个链表,那么是否可以使用递归来反转呢?答案是可以的。

既然是递归,那么我们肯定是从后向前反转。

先想最简单的情况:

如果链表只有一个元素:

只有一个元素
static Link ReverseLink1(Link head)
{
if (head.Next == null)
return head;
}

 

如果链表有两个元素呢?应该是如下方式,

设置head.Next.Next = head;(2-》1). head.Next = null; 那么逆序后的首节点就应该是head.next.

两个元素
|------|
v
|
1--*->2

 

如果后面的过程我们不用管,我们认为节点2是另一个已经做好逆序的链表的终结点,那么我们现在已经将1添加到这个逆序好的链表的终结点之后了。

两个元素
static Link ReverseLink1(Link head)
{
head.Next.Next
= head;
head.Next
= null;
return head.Next;
}

修改上面的代码:

递归逆序链表
static Link ReverseLink1(Link head)
{
if (head.Next == null)
return head;
// head.Next是逆序后的新链表的终结点,我们需要做的仅仅是将当前节点添加到该节点后,
// 而rHead是逆序后的链表的首节点,也就是原链表的终结点。
Link rHead = ReverseLink1(head.Next);
head.Next.Next = head;
head.Next
= null;
return rHead;
}

 

 

4:打印单链表的最后几个元素或者中间元素

首先,我们想想如何打印倒数第K个元素:这个有经验的人都会想到,设计两个指针,一个从链首开始遍历,一个从第K个元素开始遍历,同步遍历,当第二个指针到达链尾的时候,第一个指针正好指向倒数第k个元素。直接贴代码:(这里没有做异常输出,当元素个数少于K个情况下应该抛出异常)

打印倒数K个元素
static void Print(Link link,int lastindex)
{
int i = 0;
Link temp
= link;
while (i<lastindex&&temp!=null)
{
temp
= temp.Next;
i
++;
}
while (temp!=null)
{
temp
= temp.Next;
link
= link.Next;
}
while (link!=null)
{
Console.Write(link.Data
+" ");
link
= link.Next;
}
Console.WriteLine();
}

 

打印中间元素,其实可以设置两个指针,一个指针每次移动一步,另一个一次移动两步。当第二个指针到达链尾,第一个指针也到达了链中间。(偶数个元素的话输出2个,奇数个输出一个)

打印中间元素
static void PrintMiddle(Link link)
{
Link frist
= link.Next;
Link second
= link;
while (frist!=null&&frist.Next!=null)
{
frist
= frist.Next.Next;
second
= second.Next;
}
if (frist== null)
{
Console.WriteLine(second.Data);
}
else
{
Console.WriteLine(second.Data);
Console.WriteLine(second.Next.Data);
}
}

 

 

5:处理技巧之中间元素处理

如果一个很长的链表,我们需要在某个已知元素周围做处理,比如说删除某个元素,但是从链首遍历很耗时,这种情况下我们有一个投机的方式。我们将它的下一个节点中的数据拷贝到当前节点,然后将当前节点的next设置为下下一个节点。这样我们就伪装删除了我们当前节点,其实是删除了下一个节点,不过把下一个节点的值拷贝过来。

1:            1---》2-----。。。。。1000----》1001--》1002……..--》1000000要删除节点1000

2:            1---》2-----。。。。。1001----》1001--》1002……..--》1000000

3:            1---》2-----。。。。。1001--------------》1002……..--》1000000

删除某个元素
static void Remove(Link t)
{
if (t.Next!=null)
{
t.Data
= t.Next.Data;
t.Next
= t.Next.Next;
}
else
{
throw new Exception("该节点是链表的最后一个节点,请遍历删除!!");
}
}

 

注:上面这个算法无法删除最后一个元素,因为删除最后一个元素必须设置前一个元素的next为null,而我们根本就没有获取前一个元素的引用。

同样可以处理在某个元素之前插入一个元素:

 

在某个元素之前插入一个元素
static void Insertbefor(Link link,Link data)
{
Link temp
= new Link(null, link.Data);
link.Data
= data.Data;
temp.Next
= link.Next;
link.Next
= temp;
}