版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题目描述:
给定一个带头结点的单链表,请将其逆序。即如果单链表原来为head->1->2->3->4->5->6->7,则逆序后变为head->7->6->5->4->3->2->1
分析与解答:
由于单链表与数组不同,单链表中每个结点的地址都存储在其前驱结点的指针域中,因此对单链表中任何一个结点的访问只能从链表的头指针开始进行遍历。在对链表的操作过程中,需要特别注意在修改结点指针域的时候,记录下后继结点的地址,否则会丢失后继结点。
方法一:就地逆序
思路分析:
在遍历链表的时候,修改当前结点指针域的指向,让其指向它的前驱结点,因此需要定义一个指针变量存前驱结点的地址,为了防止后继结点丢失,还要定义一个指针变量存后继变量的地址,前驱结点和后继结点保存好后,就可以直接进行指针的逆序了。
注意:对链表首尾结点的特殊处理
代码如下:
package main
import "fmt"
//链表定义
type LNode struct {
data interface{}
next *LNode
}
func reverse(node *LNode) {
if node == nil || node.next == nil {
return
}
var pre *LNode //定义前驱结点
var cur *LNode //定义当前结点
next := node.next //把后继结点存起来,防止丢失
for next != nil { //node.next为空说明遍历到最后一个了,反之说明没有到最后
cur = next.next
next.next = pre //next指向pre,实现逆序
pre = next //后移前驱结点
next = cur //后移后驱结点
}
node.next = pre//让链表的第一个结点指向头结点
}
//创建链表
func createNode(node *LNode, max int) {
cur := node
for i := 1; i <= max; i++ {
cur.next = &LNode{} //分配内存
cur.next.data = i
cur = cur.next //向后移动指针
}
}
//打印链表
func printNode(info string, node *LNode) {
fmt.Print(info) //打印“逆序前或逆序后”
for cur := node.next; cur != nil; cur = cur.next { //遍历链表打印
fmt.Print(cur.data, " ")
}
fmt.Println()
}
func main() {
head := &LNode{}
fmt.Println("就地逆序")
createNode(head, 7)
printNode("逆序前", head)
reverse(head)
printNode("逆序后", head)
}
//结果:
//就地逆序
//逆序前1 2 3 4 5 6 7
//逆序后7 6 5 4 3 2 1
这种方法只需要对链表进行一次遍历,因此时间复杂度为O(n),但需要额外的变量保存前驱结点和后继结点,因此空间复杂度为O(1)。
方法二:插入法
思路分析:
从链表的第二个结点开始,把遍历到的结点插入到头结点的后面,直到遍历结束。假如原链表为head->1->2->3->4->5->6->7,在遍历到2的时候,将2插入到头结点的后面,链表变为head->2->1->3->4->5->6->7,同理head->3->2->1->4->5->6->7等等。
代码如下:
func reverse(node *LNode) {
if node == nil || node.next == nil {
return
}
var cur *LNode //定义当前结点
var next *LNode //后继结点
cur = node.next.next //从链表的第二个结点开始
node.next.next = nil //链表的第一个结点为尾结点
//遍历的结点依次插入到头结点的后面
for cur != nil {
next = cur.next //保存后继结点
cur.next = node.next //放到头结点后面
node.next = cur
cur = next
}
}
这种方法也是只需要遍历单链表一次,因此时间复杂度为O(n),但是与方法一比,不需要额外的指针变量保存前驱地址,效率更高。