《数据结构与算法之美》笔记:第二章-数组、链表、栈和队列

一、数组

1.数组定义的关键词:“线性表”、“连续的内存空间”、“相同类型的数据”。

2.数组的重要特性:随机访问。根据下标访问元素时的时间复杂度为O(1)。

3.对于有序数组,在k处插入一个元素时,需要将k~n的元素向后进行迁移,此时最好的情况时间复杂度为O(1),最后的情况时间复杂度为O(n)。但对于数组内容无需有序时,可将k处的元素移动至数组末尾,这样插入的时间复杂度就为O(1)。

4.对于有序数组,在k处删除一个元素时,需要将k~n的元素想前进行迁移。此时最好的情况时间复杂度为O(1),最后的情况时间复杂度为O(n)。但在特殊情况,可以将数据标记为已删除,在没有更多存储空间时,再进行一次真正的删除,减少数据迁移次数。

5.在C语言中,数组越界有可能不会出现程序报错。因为访问数组的本质是访问一段连续的内存,只要通过偏移计算得到的内存地址是可用的,即使数组访问越界也可能不会出现报错。

6.一维数组的寻址公式为:a[k]_address=base_address+k*type_size。“下标”确切的定义为“偏移”,a[0]就是相对首地址偏移为0的内存地址,a[k]就是相对首地址偏移k个type_size的内存地址。

7.对于二维数组,在C/C++中,数据是先按行,再按列的方式连续存储的。例如,arr[3][2]

存储方式为arr[0][0]-arr[0][1]-arr[1][0]-arr[1][1]-arr[2][0]-arr[2][1]。其寻址公式为arr[i][j]_address=base_address+(i*n+j)*type_size(数组arr[m][n])。

8.C++代码

#include<iostream>
using namespace std;

int main()
{
	int arr1[5] = { 99 };
	int arr2[3][2] = { {1,5},{6,4},{7,2} };

	long long base_address_arr1 = (long long)arr1;
	long long base_address_arr2 = (long long)arr2;

	cout << "arr1首地址为:" << (long long)arr1 << endl;
	cout << "arr1[0]地址为:" << (long long)&arr1[0] << endl;
	cout << "arr1[3]地址为:" << (long long)&arr1[3] << endl;
	cout << "通过寻址公式得到arr1[3]地址为:" << base_address_arr1 + 3 * sizeof(int) << endl;
	cout << endl;
	cout << "arr2首地址为:" << (long long)arr2 << endl;
	cout << "arr2[0][0]地址为:" << (long long)&arr2[0][0] << endl;
	cout << "arr2[2][1]地址为:" << (long long)&arr2[2][1] << endl;
	cout << "通过寻址公式得到arr2[2][1]地址为:" << base_address_arr2 + (2*2+1) * sizeof(int) << endl;

	return 0;
}

二、链表

1.链表并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。链表更擅长插入、删除操作。

2.C++双向链表定义代码

struct Node
{
	Node* previous;
	int data;
	Node* next;
};

struct List
{
	Node* head;
	Node* tail;
	int size;
};

3.C++双向链表初始化代码

void initList(List* list)
{
	list->head = NULL;
	list->tail = NULL;
	list->size = 0;
}

4.C++双向链表创建节点代码

Node* createNode(int data)
{
	Node* newNode = new Node();

	if (newNode == NULL)
	{
		cout << "内存分配失败!" << endl;
		exit(EXIT_FAILURE);
	}

	newNode->data = data;
	newNode->next = NULL;
	newNode->previous = NULL;

	return newNode;
}

5.C++双向链表添加节点代码

void addNode(List* list, int data)
{
	Node* newNode = createNode(data);

	if (list->head == NULL)  //第一次添加
	{
		list->head = newNode;
		list->tail = newNode;
		list->size++;

		return;
	}

	list->tail->next = newNode;
	newNode->previous = list->tail;
	list->tail = newNode;
	list->size++;
}

6.C++双向链表插入节点代码

void insertNode(List* list, int data, int position)
{
	if (position < 0)  //插入位置非法
	{
		cout << "插入位置不正确!" << endl;
		exit(EXIT_FAILURE);
	}

	Node* newNode = createNode(data);

	if (list->head == NULL)  //第一次插入
	{
		list->head = newNode;
		list->tail = newNode;
		list->size++;

		return;
	}

	if (position == 0)  //在头部插入
	{
		newNode->next = list->head;
		list->head->previous = newNode;
		list->head = newNode;

		list->size++;
	}
	else
	{
		Node* temp = list->head;
		int num = 1;
		while (num != position && temp != NULL)  //在链表找到插入位置
		{
			temp = temp->next;
			num++;
		}

		if (temp == NULL)  //插入位置非法
		{
			cout << "插入位置不正确!" << endl;
			exit(EXIT_FAILURE);
		}

		newNode->next = temp->next;
		temp->next->previous = newNode;
		temp->next = newNode;
		newNode->previous = temp;

		list->size++;
	}
}

7.C++双向链表查找元素代码

int searchNode(List* list, int target)
{
	Node* temp = list->head;
	int position = 1;
	while (temp->data != target && temp != NULL)
	{
		temp = temp->next;
		position++;
	}

	if (temp == NULL)  //未找到
	{
		cout << "链表中无法查找到该元素!" << endl;
		return -1;
	}
	else
	{
		return position;
	}
}

8.C++双向链表删除节点代码

void deleteNode(List* list, int position)
{
	if (position < 0)  //删除位置非法
	{
		cout << "插入位置不正确!" << endl;
		exit(EXIT_FAILURE);
	}

	if (list->size <= 0)  //链表中无节点
	{
		cout << "链表为空!" << endl;
		exit(EXIT_FAILURE);
	}

	if (position == 1)  //删除头节点
	{
		list->head->next->previous = NULL;
		list->head = list->head->next;
		list->size--;
	}
	else
	{
		Node* temp = list->head;
		int num = 1;
		while (temp != NULL && position != num)  //定位到要删除的位置
		{
			temp = temp->next;
			num++;
		}

		if (temp == NULL)  //删除位置非法
		{
			cout << "删除位置不存在!" << endl;
			exit(EXIT_FAILURE);
		}

		if (temp->next = NULL)  //删除最后一个节点
		{
			list->tail = temp;
			temp->next = NULL;
			list->size--;
		}
		else
		{
			temp->previous->next = temp->next;
			temp->next->previous = temp->previous;
			list->size--;
		}
	}
}

9.链表使用技巧:

(1)理解指针和引用的含义

(2)警惕指针丢失和内存泄漏

(3)利用“哨兵”简化代码

(4)留意边界问题和特殊情况

  • 如果链表为空
  • 如果链表只包含一个节点
  • 如果链表只包含两个节点
  • 如果要处理的是头节点,尾节点

(5)画图

(6)多练

三、栈

1.栈的特点是“先进后出”、“后进先出”。入栈就是在栈顶插入一个数据,出栈就是在栈顶删除一个数据。栈可分为顺序栈和链式栈。

2.栈在函数调用中的应用:在函数调用过程中,每调用一个新函数,编译器就会将调用函数的临时变量封装为栈帧并压入栈,当被调用函数执行完成并返回后,编译器就将这个函数对应的栈帧弹出栈。

四、队列

1.队列的特点是“先进先出”、“后进后出”。入队就是将数据放到队列尾部,出队就是将头部数据取出。队列可分为顺序队列和链式队列。

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值