链表的好处在于其插入和删除的时间快,但是其遍历是短板。
1.创建一个结点
struct ListNode
{
int m_nValue;
ListNode* m_pNext;
};
为了更方便的使用Node类型,可以通过 typedef 标识符进行重命名:
typedef struct ListNode
{
int m_nValue;
ListNode* m_pNext;
}Node;
2.创建一个链表
如何将结点有关系的组织起来是链表设计的关键,但是首先需要解决的问题应该是,如何创建合适的结点,然后将其添加到链表当中:
Node* creatList(int n)
{
//1.创建头结点
Node* head = new Node;
Node* pre = new Node;
pre = head;
//2.创建新结点
for(int i = 0;i < n;i++)
{
Node* node = new Node;
node->m_nValue = i;
node->m_pNext = NULL;
//3.建立关系
pre->m_pNext = node;
pre = node;
}
return head;
}
因为创建链表时,只给出了结点个数,所以需要将创建的链表的首地址返回。另外,还是一个老生常谈却容易忽略的地方,在结点创建时,使用 new 在堆空间分配内存,而不是用 Node head 在栈中申请内存,因为当这个函数被返回的时候,这个栈也会被清空,其中的数据也会丢失。
那么就需要回顾一下 new 的用法,new 关键字的意义在于申请一片连续的内存空间,并将首地址返回,这也是个为什么会需要通过一个指针来接收传回的参数了。比如,需要申请 int 型的一个数组,大小为5,那么可以写为 int* a = new int[5];,首int 代表这个指针指向的单元(操作单位)的大小,后一个 int 用来指定申请空间的大小。
3.输出链表
输出的时候要注意,我们创建的链表头指针指向的结点实际上是只作为一个空单元,并未做运算赋值,所以在输出时,应该向后移动一个 node ,然后再输出 m_nValue 的值。
void display(Node* list_head)
{
Node* head = list_head->m_pNext;
while(head != NULL)
{
cout<<head->m_nValue<<endl;
head = head->m_pNext;
}
}
4.查找一个结点
Node* searchNumber(Node* list_head,int number)
{
Node* head = list_head->m_pNext;
while(head != NULL)
{
if(number == head->m_nValue)
{
break;
}
else
{
head = head->m_pNext;
}
}
return head;
}
4.删除一个结点
结合剑指offer中的13题,我们可以发现删除会有两个差别较大的复杂度,也就对应两种思路:
- 删除某个结点,为了得到待删除结点的上一个结点,但是由于单链表无法逆序遍历,所以需要从头到尾遍历,找到待删除结点的上一个结点。然后将待删除结点的上一个结点的指针域指向待删除结点的下一个结点;再删除待删除结点。时间复杂度为 O(n) 。
- 删除某个结点,可以将其下一个结点的内容赋值给当前待删除结点,然后将待删除结点的指针域指向其下一个结点的下一个结点,然后本该删除的结点成为了待删除结点的下一个结点,此时再删除其下一个结点即完成操作。时间复杂度为O(1)。
//法一:o(n)
void deleteNode(Node* list_head,Node* find)
{
Node* pre = list_head->m_pNext;
while(pre->m_pNext != find)
{
pre = pre->m_pNext;
if(pre == NULL)
{
cout<<"NULL";
}
}
pre->m_pNext = find->m_pNext;
delete find;
}
//法二:o(1)
void deleteNode1(Node* find)
{
find->m_nValue = find->m_pNext->m_nValue;
find->m_pNext = find->m_pNext->m_pNext;
delete find->m_pNext;
find->m_pNext = NULL;
}
不得不说,法二是真的很巧妙。明明是自己要被处理掉,结果来个偷梁换柱,就将祸水东引了。
不过仅是如此还是不够的,我们需要全面的考虑这个问题,或者说对于所有链表来说,甚至对于程序设计来说都要考虑边界问题:
- 要删除的结点不是尾结点(如上法二)
- 要删除的结点是尾结点,老老实实从头到尾遍历,然后删除这最后的一个元素
- 删除的链表只有单一的结点,即将删除唯一的结点,在删除结点之后,还需要把链表的头结点设置为NULL。