数据结构入门,单链表的尾插
- 当链表为空时
- 当链表不为空时
第一种情况,当p为空时,
第二种情况:
当链表不为空时
if(p=NULL)
plist->head =s;
else
{
while(p-next!=NULL)
p=p->next;
p->next=s;
}
链表的头插
代码的实现
SListNode* s = _Buynode();
s->data = x;
s->next = plist->head;
plist->head = s;
链表尾部删除
注意:释放malloc生成的堆空间时,内存空间一直都是在的,不是说你释放之后,它就不在了哈!!!只是这块内存空间现在不归你使用了而已。
调试的过程中 data,next会变成随机值,代表了data,next*其实都已经不存在了哈!!
代码如下
if(p!=NULL)
{
if(p->next==NULL)
free(plist->head);
plist->head =NULL;
else
{
SListNode *pre=NULL;
while(p->next !=NULL)
{
pre =p;
p=p->next;
}
pre->next=NULL;
free(p);
p=NULL;
}
}
链表头部删除
链表的按值删除
按值删除,前提是要寻找到某个元素,如果找不到该元素的话,就无法删除,所以大的来说:分为2个思路:
- 1.当要删除的元素没有时
- 2.当要删除的元素存在于链表中时。
- 2.1被删除的元素位于结点中间
- 2.2被删除的元素位于结点起始位置。
代码如下所示:
SListNode *p = plist->head;
SListNode *pre = NULL;
while (p != NULL && p->data != key)
{
pre = p;
p = p->next;
}
if (p != NULL)
{
if (pre == NULL)
plist->head = p->next;
else
pre->next = p->next;
free(p);
}
分析:删除的前提是查找到元素,我们依次遍历链表,这里有个很隐晦的地方需要注意,就是while循环的判断条件,p!=NULL&&p->data !=key,这里千万不能调换顺序,因为如果找不到该元素的话,程序就会崩溃掉。
主要是因为:与连接符连接多个条件进行判断时,(以两个条件为例)当前一个条件为假,后一个就不会进行判断。
如果被查找的元素没有的话,p一直向后移动,那么查找完所有的元素,p此时等于NULL,不满足while的循环条件,那么p->data !=KEY就不会执行。而如果调换两个的顺序,p为NULL,根本不存在data,程序就会崩溃,提示无法写入数据到0x00000000处。这是需要注意的第一个点。
其次,以下图为例,要删除的元素为3,那么我们需要定义前驱指针pre,使得2和4进行相连。
如下图所示:考虑的情况为要删除的结点为中间的结点;
而如果被删除的结点为第一个结点的话,while循环一次都不会进入,那么pre=NULL,则pre就不能解引用,所以程序会崩溃。我们只需令plist->head=p->next即可删除第一个结点。(这里不可用p=p->next;因为p和plist->head虽然指向同一块空间,但是现在p指向了p->next,可是plist-head里面存放的地址仍然是最初的地址,改变A里面的值并不影响B里面的值哦)
按值删除所有相同的元素
首先给出代码:有几个需要注意:
如果要删除的元素为1,给定的链表为1 1 2 2 3,那么删除掉 1 1 之后链表里面的元素为 2 2 3 ,找不到1,所以if条件判断的时候,一定要有p!=NULL这个条件,否则就会误认为找到1值,程序会发生崩溃。
void SlistRemoveAll(Slist* plist, ELEMTYPE key)
{
SListNode *p = plist->head;
//pre=NULL,不必写入循环内部,因为是删除,只需要向后遍历即可,而排序的话,因为
//每插入1个元素,要和所有的元素都要进行比较。
SListNode *pre = NULL;
//头结点为空时,写在前面,这样一旦为空,就会返回。
if (plist->head == NULL)
return;
while (p != NULL)
{
//删除一个值
while (p != NULL && p->data != key)
{
pre = p;
p = p->next;
}
if (p != NULL) //这个条件一定不能少
{
//要删除的元素为第一个元素
if (pre == NULL)
{
plist->head = p->next;
}
else
{
pre->next = p->next;
}
free(p);
}
if (pre == NULL)
{
//要删除的元素为第一个元素,删除完毕之后,让p重新指向头结点,继续寻找。
p = plist->head;
}
else
{
//p指向已经被删除结点的下一个结点。
p = pre->next;
}
}
}
链表的按值插入
原理:原有链表为3,7,8,10,6;我们需要按值插入9,假设以从小到大的顺序进行插入,9比3大,故插入到3的后面,而9比7大,所以需要向后移动,变为3,7,9,8,10,6,因为9比8大,9向后移动一位,9比10小,将9按值插入到8和10中间。注意按值插入并不是排序。
思路:如下图所示:
链表的反转
void SlistReverse(Slist* plist)
{
SListNode *p, *q;
p = plist->head;
q = p;
//链表为空时,直接返回;
if (p == NULL)
return;
//如果只有1个结点的话,那么没必要反转
if (plist->head->next == NULL)
return;
//1.切割链表
p = p->next;
plist->head->next = NULL;
//2.头部插入
while (p!=NULL)
{
q = p->next;
p->next = plist->head;
plist->head = p;
p = q;
}
}
单链表的排序
主要需要完成三步:
1.断开第一个结点和后面的结点。
2.将后面的结点一个接一个的摘除掉
3.摘除掉之后,仿照按值插入的方式,将其插入到第一个结点的后面。
外层的while循环用来控制,让结点向后移动。内层的while循环用来控制让脱落下来的结点按照大小插入。
void SlistSort(Slist* plist)
{
assert(plist);
SListNode *p = plist->head;
if (p == NULL)
return;
//要放到循环里面,因为每次进来的话,pre都需要置为空,从头结点开始比较。
//SListNode *pre = NULL;
p = plist->head->next;
plist->head->next = NULL;
//注意这里的顺序,放在最上面就错了,因为q里面实际上存储的是plist->head的地址,
//所以下面p虽然向后移动了,但是q里面的地址并没改变,所以q的值和plist->head的值相等。
SListNode *q = p;
while (q!=NULL)
{
q = q->next;
SListNode* t = plist->head;
SListNode *pre = NULL;
while (t != NULL && p->data > t->data)
{
//记录t的上一个结点
pre = t;
t = t->next;
}
if (NULL == pre)
{
p->next=plist->head;
plist->head = p;
}
else
{
p->next = pre->next;
pre->next = p;
}
p = q;
}
}