哪里可以找到提供区块链网站开发和服务器托管的服务商?
摘要:区块链网站开发价格,哪里有网站开发服务器,做图的模板下载网站有哪些内容,免费建网站讨论一、什么是链表 链表是一种链状数据结构。简单来说,要存储的数据在内存中分别独立存放,它们之间通
区块链网站开发价格,哪里有网站开发服务器,做图的模板下载网站有哪些内容,免费建网站讨论一、什么是链表 链表是一种链状数据结构。简单来说#xff0c;要存储的数据在内存中分别独立存放#xff0c;它们之间通过某种方式相互关联。 如果我们使用C语言来实现链表#xff0c;需要声明一个结构体作为链表的结点#xff0c;结点之间使用指针关联。 二、单向链表的结… 一、什么是链表 链表是一种链状数据结构。简单来说要存储的数据在内存中分别独立存放它们之间通过某种方式相互关联。 如果我们使用C语言来实现链表需要声明一个结构体作为链表的结点结点之间使用指针关联。 二、单向链表的结构 单向链表的每个结点内都有一个指针指向下一个结点从而把所有结点串联起来。由于只有指向下一个结点的指针这种结构是单向的也就是前面的结点能找到后面的但后面的结点找不到前面的这就存在一定的问题。最后一个结点的指针是空指针标识链表的尾结点。我们只需要获取链表头部的结点的地址也就是指向链表头结点的指针就能依次找到后面的每一个结点从而管理整个链表。 说了这么多链表的每个结点应该如何定义呢很简单每个结点应该有存储数据的变量数据域和指向下一个结点的指针指针域。我们假设存储的数据类型是int。
struct SListNode
{int data;struct SListNode* next;
}; 如果要存储其他类型的数据为了修改方便可以使用typedef把int类型typedef成SLTDataType从而方便修改存储类型。
typedef int SLTDataType; 为了结构体使用方便也typedef一下。
typedef struct SListNode
{int data;struct SListNode* next;
}SLTNode; 三、打印、查找、销毁 这三个动作都要涉及一个知识点如何遍历单链表为了遍历单链表我们需要获取指向链表头结点的指针以下简称头指针。假设我们已经获取了这个指针phead每次我们都可以通过结点内的next指针找到下一个结点直到找到尾结点即next指针为NULL的结点。为此可以使用for循环遍历。
for (SLTNode* cur phead; cur; cur cur-next)
{// ...
} 打印每个结点的数据就简单了。
for (SLTNode* cur phead; cur; cur cur-next)
{printf(%d-, cur-data);
}
printf(NULL\n); 查找链表中的数据也是依次遍历即可。
for (SLTNode* cur phead; cur; cur cur-next)
{if (cur-data x)return cur;
}return NULL; // 找不到 如果要销毁链表由于我们一般把结点都存储在栈上所以使用free函数来释放空间。注意如果结点的空间被释放nextz指针就成为了野指针就找不到下一个结点了。所以遍历时应该保存要释放的结点先让cur指向next再free掉保存的结点。
for (SLTNode* cur phead; cur; )
{SLTNode* del cur;cur cur-next;free(del);
} 四、尾插、头插 如果要插入一个结点我们需要先获取一个结点前面说了一般在堆上管理结点所以使用malloc函数开辟结点。
SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));
if (newnode NULL)
{perror(malloc申请空间失败);exit(-1);
}
newnode-data x;
newnode-next NULL; 有了newnode之后需要把newnode和原链表关联起来。 先说尾插我们需要找到尾结点再让尾结点的next指向newnode。找尾结点非常简单遍历链表即可只不过当cur-next为NULL时就找到了。
SLTNode* tail phead;
for (; tail-next; tail tail-next)
{;
}
tail-next newnode; 但是上面的代码有一个严重的问题你看出来了吗代码中有tail-next的操作也就是要对tail指针解引用然而万一tail为NULL呢tail为NULL说明phead为空我们称链表phead为空的情况为空链表也就是说一上来链表为空时尾插就不能采取上面的方法。 该怎么办呢你想想此时链表里啥都没有空空如也只需要让phead指向newnode不就行了吗
if (phead NULL)phead newnode; 一般来说了解到这就足够了。但是在实现数据结构的时候我们一般把插入、删除数据等接口封装成函数也就是说我们要用一个函数实现尾插。函数的声明如下
void SListPushBack(SLTNode* phead, int x); 以上实现的完整代码如下
void SListPushBack(SLTNode* phead, int x)
{SLTNode* newnode BuySListNode(x); // 假设已经把前面讲解的获取新结点的代码封装成函数if (phead){// 链表非空// 找尾结点SLTNode* tail phead;for (; tail-next; tail tail-next){;}tail-next newnode;}else{// 空链表phead newnode;}
} 看出问题出在哪了吗phead是函数的形参对于pheadnewnode这行代码我们只是改变了形参会影响外面的实参吗不会换句话说我们把链表的头结点phead传给PushBack函数函数内部对形参phead的修改不会影响外面的实参而当PushBack函数调用结束后函数内的形参phead会被销毁这并没有完成尾插的任务 为了完成任务我们需要PushBack函数拿到phead的地址pphead才能在函数内部通过解引用pphead的方式访问函数外的phead从而修改phead。由于pphead是phead的地址不可能为NULL所以使用前都需要断言。
void SListPushBack(SLTNode** pphead, int x)
{assert(pphead);SLTNode* newnode BuySListNode(x);if (*pphead){// 链表非空// 找尾结点SLTNode* tail *pphead;for (; tail-next; tail tail-next){;}tail-next newnode;}else{// 空链表*pphead newnode;}
} 理解了尾插后头插也就简单了。由于头插无论如何都会改变头结点也就是无论如何都会改变phead如果要在函数内部实现就必须传二级指针pphead。 插入前的结构是phead-头结点。插入后的结构是phead-newnode -原来的头结点。所以只需pheadnewnode并且newnode-next原来的头结点即phead。但是两句话的顺序必须注意了如果先把phead改了就找不到原来的头结点了。你可以先思考一下两句顺序应该如何写呢如果拿到的是phead的地址pphead又应该如何写呢
newnode-next *pphead;
*pphead newnode; 思考一下需不需要考虑链表为空的特殊情况其实不用考虑因为上述操作只对newnode解引用而newnode不可能是NULL。如果还不放心简单思考一下此时代码做的事情就明白了。*pphead为NULL第一行代码使newnode的next指向了NULL第二行代码使头指针指向了newnode。 五、尾删、头删 有了前面的铺垫我们也很容易理解如果要在函数内部实现删除操作一定要传二级指针。这是因为如果删除前只有一个结点那么phead一定不为空但删除之后链表为空也就是phead为NULL此时一定要改变phead所以传参时需要传递phead的地址即pphead。 删除前必须要有一个准备工作那就是断言一下链表非空。也就是phead不为NULL即*pphead不为NULL。
assert(*pphead); 先说头删因为比较简单。只需要干掉头结点然后让phead指向新的头结点即可。注意代码的先后顺序如果头结点被释放就找不到新的头结点了即原头结点的next。所以需要保存要释放的结点让phead指向新的头结点后再释放保存的结点。
SLTNode* del *pphead;
*pphead (*pphead)-next;
free(del); 思考一下需不需要考虑删除前链表只有1个结点的特殊情况其实不需要在该情况下以上代码仍然成立只不过执行完后phead指向了NULL。 再来考虑下尾删。这是有一点挑战性的如果你第一次学习链表建议先自己实现一下再来听我讲解。 我假设你已经尝试写了。思路还是那样找到尾结点再干掉它。就完了吗No!你想想新的尾结点是谁是不是原来尾结点的前一个那这个新的尾结点的next指针原来指向的结点被你干掉了不就成野指针了吗所以还要把这个指针置成NULL。也就是说我们不仅需要找到尾结点并且把它干掉还要找到尾结点的前一个结点把这个结点的next置成NULL。 如果你一开始没想到这一点现在再想想如何找到尾结点的前一个结点呢 由于单向链表每个结点只有next没有prev前驱指针指向前一个结点的指针所以只能向后找不能向前找。找到尾结点的前一个结点tailPrev的代码如下这个思路很巧妙你能看懂吗
SLTNode* tailPrev *pphead;
for (; tailPrev-next-next; tailPrev tailPrev-next)
{;
} 其实很简单只需要想想尾结点前一个结点有什么特征在满足这个特征时跳出循环就行了。尾结点的特征时tail-nextNULL那尾结点前一个结点就要走两步才能走到NULL即tailPrev-next-nextNULL所以就有了上面的代码。 但是这个代码忽视了一个特殊情况那就是如果链表只有一个结点也就是phead-nextNULL由于一开始tailPrevphead此时tailPrev-next-nextphead-next-nextNULL-next对空指针解引用了程序会崩溃所以要对这个特殊情况单独处理你想想怎么处理很简单嘛只有一个结点了只需要free掉这个结点再把phead置成NULL就行了 六、插入删除的一般化 如果我们想要在任意位置插入或者删除呢有了前面的铺垫这个问题就不难了无非是链接一些结点或者是干掉一些结点。由于总会有改变phead的情况所以以下均需要使用二级指针pphead。 先说插入。插入分两种情况一种是在pos前面插入一种是在pos后面插入你觉得哪种更简单如果是在前面插入你怎么找到pos前面的结点那还要从头结点一个一个往后找多麻烦所以肯定是在后面插入简单。 前插的思路找pos前面的结点需要prev从phead开始一个一个往后找直到prev-nextpos就找到了。找到后使得prev-nextnewnodenewnode-nextpos就行了。思考一下需不需要考虑先后顺序其实不用因为prevnewnodepos是3个独立的结点相互之间互不影响。
SLTNode* prev *pphead;
for (; prev-next ! pos; prev prev-next)
{;
}newnode-next pos;
prev-next newnode; 需要考虑一种特殊情况头部的插入需要改变phead由于头插前面讲过这里就不重复了。 后插就简单了有了前面这么多的铺垫你应该也可以写出来。注意代码的先后顺序 newnode-next pos-next;pos-next newnode; 至于删除也分为两种情况分别是删除pos结点和删除pos之后的结点。想一想哪种更简单删除pos位置的结点你还需要找到pos之前的结点会更复杂一些。 删除pos位置的结点的思路先找到pos之前的结点和pos之后的结点连接这两个结点干掉pos。注意代码的先后顺序
SLTNode* prev *pphead;
for (; prev-next ! pos; prev prev-next)
{;
}prev-next pos-next;
free(pos); 这里提一句如果你不想总要考虑代码的先后顺序可以先保存prev和nextpos-next再让prev-nextnext。 这里需要考虑一种特殊情况如果pheadpos即头删的情况prev找到的就不是pos前面的结点了此时需要单独处理由于头删的情况前面已经讲解过这里不再重复。 最后删除pos之后结点就非常简单了。你可以自己写一下然后对照后面的代码。 assert(pos-next); // pos后至少有1个结点SLTNode* del pos-next;pos-next del-next;free(del); 你是否考虑了对pos-next的断言呢如果没考虑到请好好反省一下。删除pos后面的结点就说明了pos后面必须有结点也就是pos-next不为NULL 七、总结 单向链表虽然结构很简单但使用起来可真麻烦啊。所以这种结构是有一定缺陷的。事实上这种链表结构的全称是单向不循环不带头链表具有一定的局限性。
