3.双向链表&静态链表

目录

一 双向链表

1.1 双向链表概述

1.2 双向链表的优势

1.3 结构体及宏定义

1.4 初始化链表

1.5 头插

1.6 删除结点

1.7 遍历链表

1.8 测试代码

1.9 运行结果

1.10 临摹老师的代码

1.11 思考&总结

二 静态链表

         2.1 概述

2.2 优点&缺点

2.3 示意图

2.4 原理

2.5 结构体&宏定义

2.6 静态链表的初始化

2.7 遍历

2.8 头插

2.9 回收空间

2.10 头删

2.11 测试代码与结果

2.12 总结&反思


一 双向链表

1.1 双向链表概述

        双向链表就是在单链表的基础上,在定义结构体的时候做了一些手脚,在单链表中结构体的内容为数据以及指向下一个结点的指针,在双向链表中,增加了指向前一个结点元素的指针,即实现了对指定元素前驱后继的查询。代价就是增加了1/3的空间储存。

        内存分配图如下:

        

1.2 双向链表的优势

        双向链表实现了一个十分重要的功能是自我删除。即:

p->previous->next=p->next;

        另外定义的Note来储存前后结点的信息。

1.3 结构体及宏定义

typedef char ElemType;

#define OK 1
#define ERROR 0

typedef struct DoubleLinkedNote
{
    ElemType data;
    struct DoubleLinkedNote *Previous;
    struct DoubleLinkedNote *Next;
} DLNote, *DLNotePtr, *DoubleLink;

        在老师命名基础上修改了一点,比如DLNotePtr代表结点,DoubleLink代表链表,使程序具有更好的可读性

1.4 初始化链表

/*初始化*/
DoubleLink Init()
{
    DLNotePtr tempHeader;
    tempHeader = (DLNotePtr)malloc(sizeof(DLNote));
    tempHeader->data = 0;
    tempHeader->Previous = NULL;
    tempHeader->Next = NULL;
    return tempHeader;
}

1.5 头插

/*头插*/
int InsertElement(DoubleLink tempHeader, ElemType data)
{
    DLNotePtr p, q, r;
    p = tempHeader;
    if (p->Next == NULL)
    {
        /*  当双向链表只有一个头结点时 */
        r = (DLNotePtr)malloc(sizeof(DLNote));
        r->data = data;    //  数据域赋值
        r->Next = p->Next; //  后向指针连接
        p->Next = r;
        r->Previous = p; //  将插入结点的Previous 指向头结点
    }
    else
    {
        /* 当双向链表不只一个头结点时 */
        r = (DLNotePtr)malloc(sizeof(DLNote));
        r->data = data;        //  数据域赋值
        p->Next->Previous = r; //  头结点的下一个结点的Previous 指针
        r->Previous = p;       //  插入的结点的Previous 指针指向头结点
        r->Next = p->Next;     //  插入结点的Next 指针指向原本头指针的下一结点
        p->Next = r;           //  将头结点的Next 指针指向插入结点
    }
}

        之前一直尝试的是尾插法,这次采用了头插的方式。值得注意的是,一定要分类讨论当前链表是否只存在一个结点。

1.6 删除结点

/*删除指定元素*/
int DeleteElem(DLNotePtr tempHeader, ElemType data)
{
    DLNotePtr p, q, r;
    p = tempHeader;
    if (p == NULL)
    {
        printf("The list is empty\n");
        return ERROR;
    }
    while (p->Next != NULL)
    {
        p = p->Next;
        if (p->data == data)
        {
            p->Previous->Next = p->Next;     //  将被删结点的上一个结点的Next 指针指向 被删结点的下一个结点
            p->Next->Previous = p->Previous; //  将被删结点的下一个结点的 Previous 指针指向 被删结点的上一个结点
            break;
        }
    }
    free(p); //  释放内存
    return OK;
}

1.7 遍历链表

/*遍历打印*/
int Print(DoubleLink tempHeader)
{
    DLNotePtr p, q;
    p = tempHeader->Next;
    while (p)
    {
        printf("%c ", p->data);
        p = p->Next;
    }
    printf("\n");
    return OK;
}

1.8 测试代码

int main()
{
    DoubleLink my_List;
    my_List = Init();

    /*插入*/
    InsertElement(my_List, 'a');
    InsertElement(my_List, 'b');
    InsertElement(my_List, 'c');
    InsertElement(my_List, 'd');
    Print(my_List);

    /*删除指定元素*/
    DeleteElem(my_List, 'a');
    Print(my_List);
    DeleteElem(my_List, 'b');
    Print(my_List);
    DeleteElem(my_List, 'e');
    Print(my_List);
    /*销毁链表*/
    DestroyList(my_List);
    Print(my_List);
    return 0;
}

1.9 运行结果

 

1.10 临摹老师的代码

      

#include <stdio.h>
#include <malloc.h>

/**
 * Double linked list of integers. The key is char.
 */
typedef struct DoubleLinkedNode{
	char data;
	struct DoubleLinkedNode *previous;
	struct DoubleLinkedNode *next;
} DLNode, *DLNodePtr;

/**
 * Initialize the list with a header.
 * @return The pointer to the header.
 */
DLNodePtr initLinkList(){
	DLNodePtr tempHeader = (DLNodePtr)malloc(sizeof(struct DoubleLinkedNode));
	tempHeader->data = '\0';
	tempHeader->previous = NULL;
	tempHeader->next = NULL;
	return tempHeader;
}// Of initLinkList

/**
 * Print the list.
 * @param paraHeader The header of the list.
 */
void printList(DLNodePtr paraHeader){
	DLNodePtr p = paraHeader->next;
	while (p != NULL) {
		printf("%c", p->data);
		p = p->next;
	}// Of while
	printf("\r\n");
}// Of printList

/**
 * Insert an element to the given position.
 * @param paraHeader The header of the list.
 * @param paraChar The given char.
 * @param paraPosition The given position.
 */
void insertElement(DLNodePtr paraHeader, char paraChar, int paraPosition){
	DLNodePtr p, q, r;

	// Step 1. Search to the position.
	p = paraHeader;
	for (int i = 0; i < paraPosition; i ++) {
		p = p->next;
		if (p == NULL) {
			printf("The position %d is beyond the scope of the list.", paraPosition);
			return;
		}// Of if
	} // Of for i

	// Step 2. Construct a new node.
	q = (DLNodePtr)malloc(sizeof(struct DoubleLinkedNode));
	q->data = paraChar;

	// Step 3. Now link.
	r = p->next;
	q->next = p->next;
	q->previous = p;
	p->next = q;
	if (r != NULL) {
		r->previous = q;
	}// Of if
}// Of insertElement

/**
 * Delete an element from the list.
 * @param paraHeader The header of the list.
 * @param paraChar The given char.
 */
void deleteElement(DLNodePtr paraHeader, char paraChar){
	DLNodePtr p, q, r;
	p = paraHeader;

	// Step 1. Locate.
	while ((p->next != NULL) && (p->next->data != paraChar)){
		p = p->next;
	}// Of while

	// Step 2. Error check.
	if (p->next == NULL) {
		printf("The char '%c' does not exist.\r\n", paraChar);
		return;
	}// Of if

	// Step 3. Change links.
	q = p->next;
	r = q->next;
	p->next = r;
	if (r != NULL) {
		r->previous = p;
	}// Of if

	// Step 4. Free the space.
	free(q);
}// Of deleteElement

/**
 * Unit test.
 */
void insertDeleteTest(){
	// Step 1. Initialize an empty list.
	DLNodePtr tempList = initLinkList();
	printList(tempList);

	// Step 2. Add some characters.
	insertElement(tempList, 'H', 0);
	insertElement(tempList, 'e', 1);
	insertElement(tempList, 'l', 2);
	insertElement(tempList, 'l', 3);
	insertElement(tempList, 'o', 4);
	insertElement(tempList, '!', 5);
	printList(tempList);

	// Step 3. Delete some characters (the first occurrence).
	deleteElement(tempList, 'e');
	deleteElement(tempList, 'a');
	deleteElement(tempList, 'o');
	printList(tempList);

	// Step 4. Insert to a given position.
	insertElement(tempList, 'o', 1);
	printList(tempList);
}// Of appendInsertDeleteTest

/**
 * Address test: beyond the book.
 */
void basicAddressTest(){
	DLNode tempNode1, tempNode2;

	tempNode1.data = 4;
	tempNode1.next = NULL;

	tempNode2.data = 6;
	tempNode2.next = NULL;

	printf("The first node: %d, %d, %d\r\n",
		&tempNode1, &tempNode1.data, &tempNode1.next);
	printf("The second node: %d, %d, %d\r\n",
		&tempNode2, &tempNode2.data, &tempNode2.next);

	tempNode1.next = &tempNode2;
}// Of basicAddressTest

/**
 * The entrance.
 */
void main(){
	insertDeleteTest();
	basicAddressTest();
}// Of main

1.11 思考&总结

        虽然双向链表只是比单向链表在结构体增加了一个前驱的指针,但是在插入操作的时候,指针的赋值顺序变得更加重要。一不小心就会出先乌龙。

        此外,每写完一个函数就要去测试啊!千万不要最后测试,要不会输的很惨,调试太难了。

        此外,由于头结点的前驱指向了NULL,在链表只存在头节点进行插入和删除操作的时候,要特别地处理这个NULL,因为和其他常规结点不一样,指向NULL,把NULL赋给一个指针变量,可能会导致不可预估地错误。

二 静态链表

2.1 概述

        逻辑结构上相邻的数据元素,存储在指定的一块内存空间中,数据元素只允许在这块内存空间中随机存放,这样的存储结构生成的链表称为静态链表。静态链表的特点在于把顺序表和单链表地优点结合了起来。但同时也继承了其中的一些缺点。

2.2 优点&缺点

        优点:删除和插入元素时间复杂度低;

        缺点:需要提前分配一块较大的空间。无法进行动态地操作

2.3 示意图

        

 

2.4 原理

        静态链表实际上存在两个单链表。一个是用于管理已用结点,头节点通常为下标为0的元素。另外一个是管理未用结点的,头节点通常为下标为1的元素。

        仔细观察上图,我们可以发现,区别于朴素的数组,静态链表中的结点使用自己的下标来代表自己的位置,同时定义了一个int型变量来储存游标元素,所谓游标也就是指的是指向下一个位置的下标。这样就在一个固定的空间内实现了类似单链表的操作。

         OK,我们来看代码:

2.5 结构体&宏定义

#define MAX 100
#define ElemType char

typedef struct ListNode
{
	ElemType data;
	int      cur;   //静态链表中的游标
}ListNode;

//静态链表实际上就是一个结构体数组
typedef ListNode StaticList[MAX]; 

2.6 静态链表的初始化

//静态链表的初始化
void InitSList(StaticList &p)
{
	for(int i=1; i<MAX; ++i) 
	{
		p[i].cur = i+1; 
	}
	p[MAX].cur = 0;
	p[0].cur = -1;  
}

        需要注意对下标为0和下标为1的元素做处理。

2.7 遍历

//遍历
void PrintList(StaticList &p)
{
	int i = p[0].cur;//从第一个数据结点开始搜索
	while(i != -1)//将所有的数据结点打印
	{
		printf("%c-->",p[i].data);
		i = p[i].cur;
	}
	printf("Nul.\n");
}

2.8 头插

//静态链表的头插
void Insert(StaticList &p, ElemType x)
{
    int i = Malloc_SL(p);
    if (i == 0)
    {
        printf("申请节点空间失败.\n");
        return;
    }
    p[i].data = x;

    if (p[0].cur == -1)
    {
        p[i].cur = -1;
        // p[0].cur = i;
    }
    else
    {
        p[i].cur = p[0].cur;
    }
    p[0].cur = i;
}

        需要去考虑申请空间不足的情况,比较这是一个固定大小的空间。

2.9 回收空间

        

//回收空间
void Free_SL(StaticList &p, int k)
{
	p[k].cur = p[1].cur;
	p[1].cur = k;
}

        也就需要处理自己的cur值。

2.10 头删


void Delete(StaticList &p)
{
	int i = p[0].cur;
	p[0].cur = p[i].cur;
	Free_SL(p,i);
}

        头删的过程中也处理了free。

2.11 测试代码与结果

int main()
{
	StaticList SL;  
	InitSList(SL);  
	for(int i=0; i<8; ++i)
	{
		Insert(SL,'A'+i);
	}
	ShowSList(SL);	
	Delete(SL);
    ShowSList(SL);
    Insert(SL,'Z');
    ShowSList(SL);
    return 0;
}

 2.12 总结&反思

        静态链表是一个小型的操作系统管理内存的缩影。其中核心的问题就是注意是否超内存,以及对已经使用内存的回收。

        另外我们单独实现了内存单元的FREE,同时也要注意在头删函数中本身就已经存在了FREE操作。

        对于查询和删除以及插入操作,数据量大的时候十分友好。

        以上。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值