数据结构之单链表一
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;
}
单
浙公网安备 33010602011771号