链表概述
链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址。因此,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
链表的各类操作包括:单向链表的创建、删除(头删、尾删)、 插入(头插、C和C++尾插)、输出、 查找某一个结点、指定位置删除某结点等。
各种操作代码如下:
#include"SListNode.h"SListNode* _BuyNode(DataType x)//开辟新节点{ SListNode* tmp = (SListNode*)malloc(sizeof(SListNode)); tmp->data = x; tmp->next = NULL; return tmp;}void PrintSList(SListNode* & pHead)//打印单链表{ SListNode* cur = pHead; if (pHead == NULL) { printf("SListNode is NULL!\n"); return; } while (cur) { printf("%d->", cur->data); cur = cur->next; } printf("NULL");}void DestoryList(SListNode* & pHead)//释放空间{ SListNode* cur = pHead; while (cur) { SListNode* tmp = cur; cur = cur->next;//顺序不可以颠倒 free(tmp); } pHead = NULL;}void PushBack_C(SListNode** pHead, const DataType x)//c尾插{ //情况1.单链表为空;情况2.单链表不为空 assert(pHead); if (*pHead == NULL) *pHead = _BuyNode(x); else { SListNode* tail = *pHead; while (tail->next != NULL)//找尾 tail = tail->next; tail->next = _BuyNode(x); }}void PushBack_Cpp(SListNode* & pHead, const DataType x)//c++尾插,使用引用{//引用相当于定义一个别名,不需断言指针 //情况1.单链表为空;情况2.单链表不为空 if (pHead == NULL) pHead = _BuyNode(x); else { SListNode* tail = pHead; while (tail->next != NULL)//找尾 tail = tail->next; tail->next = _BuyNode(x); }}void PopBack(SListNode* & pHead)//尾删{ // 注意:单链表为空、一个节点以及多个节点的情况 if (pHead == NULL) { printf("SListNode is NULL!\n"); return; } else if (pHead->next == NULL) {//删除节点时置空并释放空间 pHead = NULL; free(pHead); } else { SListNode* tail = pHead; while (tail->next->next != NULL) {//1 2 3 4删除4需要找到3(tail) tail = tail->next; } tail->next = NULL; free(tail->next); }}void PushFront(SListNode* & pHead, const DataType x)//头插{//单链表为空、不为空 if (pHead == NULL) pHead = _BuyNode(x); else {//利用两个指针pHead、tmp移动 SListNode* tmp = _BuyNode(x); tmp->next = pHead;//针对没有头结点的情况 pHead = tmp; }}void PopFront(SListNode* & pHead)//头删{ // 注意:单链表为空、一个节点以及多个节点的情况 if (pHead == NULL) { printf("SListNode is NULL!\n"); return; } else { SListNode* newhead = pHead->next; free(pHead); pHead = newhead; }}SListNode* Find(SListNode* pHead, const DataType x)//查找某数结点{ SListNode* cur = pHead; while (cur) { if (cur->data == x) return cur; cur = cur->next; } printf("%d Position is not exist!", x); return NULL;}void Insert(SListNode* pos, const DataType x)//指定位置pos后插入新结点{ assert(pos);//断言pos有效性 SListNode* tmp = _BuyNode(x); tmp->next = pos->next; pos->next = tmp;}void Erase(SListNode* & pHead, SListNode* pos)//删除某结点{//考虑单链表是否空、pos是否为首元节点 assert(pos); if (pos == pHead) { pHead = pHead->next; free(pos); } SListNode* prev = pHead; while (prev) {//1 2 3 4删除第三个节点3需找到其前一个节点2 if (prev->next == pos) { prev->next = pos->next; pos = NULL; free(pos); return; } prev = prev->next; }}
各函数测试用例如下:
#include"SListNode.h"void Test1(){//尾插尾删 SListNode *phead = NULL;//注意需要置空 PushBack_C(&phead, 1); PushBack_C(&phead, 2); PushBack_Cpp(phead, 3); PushBack_Cpp(phead, 4); PrintSList(phead); PopBack(phead); PopBack(phead); printf("\n---------------------\n"); PrintSList(phead); PopBack(phead); PopBack(phead); printf("\n---------------------\n"); PopBack(phead); printf("\n---------------------\n"); PrintSList(phead); DestoryList(phead);}void Test2(){//头插头删 SListNode *phead = NULL; PushFront(phead, 1); PushFront(phead, 2); PushFront(phead, 3); PushFront(phead, 4); PrintSList(phead); PopFront(phead); PopFront(phead); printf("\n---------------------\n"); PrintSList(phead); PopFront(phead); PopFront(phead); printf("\n---------------------\n"); PopFront(phead); printf("\n---------------------\n"); PrintSList(phead); DestoryList(phead);}void Test3(){//查找某数节点、插入/删除新结点 SListNode *phead = NULL; PushBack_Cpp(phead, 1); PushBack_Cpp(phead, 2); PushBack_Cpp(phead, 3); PushBack_Cpp(phead, 5); PushBack_Cpp(phead, 7); PushBack_Cpp(phead, 6); PrintSList(phead); printf("\n---------------------\n"); printf("%d\n", Find(phead, 3)->data); printf("\n---------------------\n"); Insert(Find(phead, 3), 4); PrintSList(phead); printf("\n---------------------\n"); Erase(phead, Find(phead, 7)); PrintSList(phead); DestoryList(phead);}int main(){ Test1(); printf("\n***********************\n"); Test2(); printf("\n***********************\n"); Test3(); system("pause");}
头文件如下:
#pragma once#ifndef __SLISTNODE_H__#define __SLISTNODE_H__#define _CRT_SECURE_NO_WARNINGS 1#include#include #include typedef int DataType; typedef struct SListNode{ DataType data; struct SListNode* next;}SListNode;SListNode* _BuyNode(DataType x);//开辟新节点void PrintSList(SListNode* & pHead);//打印单链表void PushBack_Cpp(SListNode* & pHead, const DataType x);//c++尾插void PushBack_C(SListNode** pHead, const DataType x);//c尾插void PopBack(SListNode* & pHead);//尾删void PushFront(SListNode* & pHead, const DataType x);//头插void PopFront(SListNode* & pHead);//头删SListNode* Find(SListNode* pHead, const DataType x);//查找某数结点void Insert(SListNode* pos, const DataType x);//指定位置pos后插入新结点void Erase(SListNode* & pHead, SListNode* pos);//删除某结点void DestoryList(SListNode* & pHead);//释放空间void PrintTailToHead(SListNode* pHead);//从尾到头打印单链表void DelNonTailNode(SListNode* pos);//删除一个无头单链表的非尾节点void InsertFrontNode(SListNode* pos, DataType x);//在无头单链表的一个非头结点前插入一个节点SListNode* Rverse(SListNode* pHead);//逆置/反转单链表SListNode* FindMidNode(SListNode* pHead);//查找单链表的中间节点,要求只能遍历一次链表SListNode* FindMidNode(SListNode* pHead, DataType k);//查找单链表的倒数第k个节点,要求只能遍历一次链表SListNode* MergeList(SListNode* L1, SListNode* L2);//合并两个有序链表,合并后依然有序void BubbleSort(SListNode* pHead);//冒泡排序单链表SListNode* JosephCycle(SListNode* pHead, int k);//单链表实现约瑟夫环//判断单链表是否带环?若带环,求环长度,求环入口点SListNode* CheckCycle(SListNode* pHead);//检查是否带环int GetCycleLength(SListNode* MeetNode);//求环的长度SListNode* GetEntryNode1(SListNode* pHead);//求环的入口地址SListNode* GetEntryNode2(SListNode* pHead);//求环的入口地址#endif //__SLISTNODE_H__
链表操作的其他实现见本人博客:
【面试题】单链表的操作1
【面试题】单链表的操作2
堆栈的简述:
1、管理方式和碎片问题
对于栈来讲,是由编译器自动管理,无须手工控制;对于堆来说,释放工作由程序员来控制,容易产生内存碎片;对于堆,频繁的new / delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序的效率降低。对于栈来讲,则不会出现这样的问题,因为栈是先进后出的队列,不可能有一个非栈顶的内存块从栈中间弹出,在它弹出之前,它上面后进的栈内容已经被弹出,因此不会出现不连续的碎片。
-----------------------------------------------------------------------------------------
2、分配效率
栈是系统提供的数据结构,计算机会在底层对栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C / C++提供的,它的机制相对复杂;例如分配一块内存,会按照一定的算法(内存分配算法)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的内存空间(可能由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低很多。
-----------------------------------------------------------------------------------------
3、增长的方向不同
栈内存由一个栈指针esp来开辟和回收,栈内存是从高地址向低地址增长的,增长时,栈指针是向低地址方向移动,指针的地址值也就相应的减小;回收时,栈指针向高地址方向移动,地址值也就增加。所以栈内存的开辟与回收都只是指针的加减。
对于堆来讲,增长的方向是向上的,也就是向着内存高地址的方向移动;回收时,指针向低地址方向移动,地址值也就减小。
-----------------------------------------------------------------------------------------
4、空间大小不同
一般来讲32位的系统下,堆内存可以达到4GB的空间,从这个角度来看堆内存几乎是没什么限制的。但是对于栈来讲,一般都有一定的空间大小。无论是堆还是栈,都要防止越界现象的发生,因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生意想不到的结果。