链表的操作一直是困扰着本人,于是还是自己总结一番,针对性的加强训练。
下面是自己的一点点总结:
链表其实就是多个数据块通过指针连接起来,所谓的链表的操作,最核心也是最困难的就是调整指针的指向,每个结点只能也只有它能知道下一个结点的存储位置。改变链表的顺序就是不断的调整每个数据块的指针指向。因此,除非需要创建新的结点,会使用直接创建Node结点,否则一般都是创建Node类型的指针,通过指针不断的操作每个数据块中指针的指向,从而达到操作链表顺序的目的。
一些链表的复杂操作也基本上是由上面的操作组合构成。
链表主要分为:
单链表:单链表每个节点只包含一个后驱指针
双链表:双链表节点同时包含一个前驱指针和一个后驱指针
循环链表:循环链表的尾节点的后驱指向头节点
此外还需要注意的是:单链表还包括带头节点与不带头结点的两种。两者的差别不大,主要是为了代码操作的方便。
链表的创建:
单链表结点定义:
typedef struct Lnode
{
ElemType data; /*数据域,保存结点的值 */
struct Lnode *next; /*指针域*/
}LNode; /*结点的类型 */
创建单链表:
头插入法:从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。即每次插入的结点都作为链表的第一个结点。生成的链表中结点的次序和输入的顺序相反。
尾插入法:将新结点插入到当前链表的表尾,使其成为当前链表的尾结点。生成的链表中结点的次序和输入的顺序相同。
- 使用头插入法创建带头结点的单链表:
LNode* CreatLinkList()
{
LNode *head = new LNode;//头结点包含实实在在的数据域,并不是一个空指针。
head->next = NULL;
for (int i=0;i<100;i++)
{
LNode* TempNode = new LNode;
TempNode->data = i;
TempNode->next = head->next;
head->next = TempNode;
}
return head;
}
- 使用头插入法创建不带头结点的单链表:
LNode* CreatLinkListNoHead()
{
LNode* head = NULL;
for (int i=0;i<100;i++)
{
LNode* TempNode = new LNode;
TempNode->data = i;
if(NULL == head)
{
head = TempNode;
head->next = NULL;
}
else
{
TempNode->next = head;
head = TempNode;
}
}
return head;
}
//下面的方法也可以
LNode* CreatLinkListNoHead1()
{
LNode* head = NULL;
for (int i=0;i<100;i++)
{
LNode* TempNode = new LNode;
TempNode->data = i;
TempNode->next = head;
head = TempNode;
}
return head;
}
- 使用尾插法创建带头结点的单链表:
LNode* CreatLinkListTail()
{
LNode* head = new LNode;
LNode* CurNode = head;
CurNode->next = NULL;
for (int i=0;i<100;i++)
{
LNode* TempNode = new LNode;
TempNode->data = i;
TempNode->next = CurNode->next;
CurNode->next = TempNode;
CurNode = TempNode;
}
return head;
}
- 使用尾插法创建不带头结点的单链表:
LNode* CreatLinkListTailNoHead()
{
LNode* head = NULL;
LNode* CurNode = head;
for (int i=0;i<100;i++)
{
LNode* TempNode = new LNode;
TempNode->data = i;
if (NULL == head)
{
head = CurNode = TempNode;
TempNode->next = NULL;
}
else
{
TempNode->next = CurNode->next;
CurNode->next = TempNode;
CurNode = TempNode;
}
}
return head;
}
LNode* CreatLinkListTailNoHead()
{
LNode* head = NULL;
LNode* CurNode = head;
for (int i=0;i<100;i++)
{
LNode* TempNode = new LNode;
TempNode->data = i;
TempNode->next = NULL;
if (head == NULL)
{
head=CurNode = TempNode;
}
else
{
CurNode->next = TempNode;
CurNode = TempNode;
}
}
return head;
}
对于单链表,无论是哪种操作,只要涉及到钩链(或重新钩链),如果没有明确给出直接后继,钩链(或重新钩链)的次序必须是“先右后左”。
当一个指针为NULL时,千万不要使用此指针进行操作,一定要进行判断,例如
LNode* head = NULL;
head->next = NULL;
就是大错特错的。
此外通过上述操作可以发现,不带头结点的时候需要对头结点为空的情形进行特殊处理。带有头结点的单链表只不过浪费了一个结点的数据存储域,(不一定是浪费,这个数据域可用来存储一些需要的数据,例如单链表的长度等)在操作的时候不需要判断头结点为空的情形,代码更加简洁有效。头结点的数据域可以不存储任何信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。头结点的作用是使所有链表(包括空表)的头指针非空,并使对单链表的插入、删除操作不需要区分是否为空表或是否在第一个位置进行,从而与其他位置的插入、删除操作一致。
单链表的插入操作:
- 带有头结点的单链表插入操作:
bool InsertValue(LNode* head,int Index,int KeyValue)
{
if (head == NULL || Index < 0)
{
return false;
}
LNode *CurNode = head;
int i = -1;
while (CurNode!=NULL && i<Index-1)
{
CurNode= CurNode->next;
i++;
}
if (i==Index-1)
{
LNode* TempNode = new LNode;
TempNode->data = KeyValue;
TempNode->next = CurNode->next;
CurNode->next = TempNode;
return true;
}
}
插入时一定要考察参数的有效性,其中就包括Index的合法性等,同时插入操作需要问清楚面试官的要求,比如插入位置为0时是不是插入第一个结点。这牵涉到计数器i的初始值。
- 不带头结点的单链表的插入操作:
bool InsertValueNohead(LNode*& head,int Index,int Key)
{
if (head ==NULL || Index <0)
{
return false;
}
if (Index == 0)
{
LNode* TempNode = new LNode;
TempNode->data = Key;
TempNode->next = head;
head = TempNode;
return true;
}
LNode * CurNode = head;
int i = 0;
while (CurNode!=NULL && i<Index-1)
{
CurNode= CurNode->next;
i++;
}
if (i==Index-1)
{
LNode* TempNode = new LNode;
TempNode->data = Key;
TempNode->next = CurNode->next;
CurNode->next = TempNode;
return true;
}
return false;
}
上述代码中需要注意的是:
- 需要判断插入位置是否是头结点,如果是头结点的话需要单独操作,这就是与带头结点单链表的区别,因为带头结点的单链表的第一个结点始终都不会改变,而不带头结点的单链表的第一个结点有可能被改变。
- 头结点需要改变的时候,由于返回值是bool类型,因此在传值的时候使用了引用类型。否则会没有效果。
单链表的删除操作:
单链表的删除操作可以分为:按序号删除结点,按值删除结点
按序号删除:
带头结点的按序号删除:
bool DeleteNode(LNode* head,int Index)
{
if (head == NULL || Index <= 0)
{
return false;
}
LNode * CurNode = head;
int i=0;
while (CurNode!=NULL && i<Index-1)
{
CurNode = CurNode->next;
i++;
}
if (i != Index-1)
{
return false;
}
LNode* p = CurNode->next;
CurNode->next = p->next;
delete p;
return true;
}
不带头结点的单链表按序号删除:
bool DeleteNodeNoHead(LNode*& head,int Index)
{
if (head==NULL || Index<=0)
{
return false;
}
if (Index == 1)
{
LNode* Node = head;
head = head->next;
delete Node;
}
else
{
LNode * CurNode = head;
int i=1;
while (CurNode!=NULL && i<Index-1)
{
CurNode = CurNode->next;
i++;
}
if (i != Index-1)
{
return false;
}
LNode* p = CurNode->next;
CurNode->next = p->next;
delete p;
return true;
}
}
带头结点按值删除操作:
bool DeleteNodeKey(LNode* head, int key)
{
if (head == NULL)
{
return false;
}
LNode* CurNode = head;
LNode* TempNode = NULL;
while ( CurNode->next!=NULL && CurNode->next->data != key)
{
CurNode = CurNode->next;
}
if (CurNode->next->data == key)
{
TempNode = CurNode->next;
CurNode->next= TempNode->next;
delete TempNode;
return true;
}
return false;
}
不带头结点的按值删除操作
bool DeleteNodeKeyNoHead(LNode*& head,int key)
{
if (head == NULL)
{
return false;
}
if (head->data == key)
{
LNode* Node = head;
head = Node->next;
delete Node;
return true;
}
else
{
LNode* CurNode = head;
LNode* TempNode = NULL;
while ( CurNode->next!=NULL && CurNode->next->data != key)
{
CurNode = CurNode->next;
}
if (CurNode->next->data == key)
{
TempNode = CurNode->next;
CurNode->next= TempNode->next;
delete TempNode;
return true;
}
return false;
}
}
关于链表的插入或者删除操作,都要首先找到要删除结点的前一个结点。因为每个结点所包含的的链接信息只是它的下一个结点,其余的信息一概不知。