您的位置:首页 > 理论基础 > 数据结构算法

数据结构和算法学习笔记(1)

2010-02-04 18:49 525 查看
抓紧时间学习了,一步一个脚印,绝不松懈,这是本系列的第一篇,算法和数据结构是程序员你的必修课,面试看懂别人复杂的程序这些都是基本功,需要加强,所以有了下面的文章,2010.2.4 6.24分

数据结构和算法学习笔记(1)

首先给出一个最基本的结论:我们做算法和结构其实说白了,就是空间换时间,或者时间换空间。要看需求了。一般的情况下,大家都喜欢空间换时间。因为内存大嘛,总是用啊用不完。

最基本的一些结构就略过了,从栈开始吧。

1 堆栈

堆栈即为栈,下面是一个数组实现的栈:该栈固定大小,即存放的元素最大值固定,数组第0位表示栈底。如果需要存入不确定数量的数据,请使用链表来实现栈。

class Stack

{

public:

Stack(int iAmount = 10); //设置栈所在数组的最大值

~Stack();

int Pop(int&iVal); //出栈

int Push(intiVal); //入栈

int Top(int&iVal); //栈顶元素

private:

int *m_pData; //栈所在数据首地址。。简单起见用int应该改用泛型

int m_iCount; //使用的元素个数

int m_iAmount; //栈最大元素个数

};

Stack::Stack(int iAmount)

{

m_pData= new int[iAmount];//连续的数组,其中固定大小来划分栈中元素

m_iCount= 0; //初始化栈内元素为0个

m_iAmount= iAmount;

}

Stack::~Stack()

{

delete m_pData;

}

int Stack::Pop(int&iVal)

{

if(m_iCount>0)

{

--m_iCount; //栈内元素--

iVal= m_pData[m_iCount];

return 1;

}

return 0;

}

int Stack::Push(int iVal)

{

if(m_iCount<m_iAmount)

{

m_pData[m_iCount]= iVal;

++m_iCount;

return 1;

}

return 0;

}

int Stack::Top(int&iVal)

{

if(m_iCount>0 && m_iCount<=m_iAmount)

{

iVal= m_pData[m_iCount-1];

return 1;

}

return 0;

}

2 队列

下面是一个利用队列来对树进行广度优先检索。

广度优先区别于深度优先,即优先遍历最靠近根节点的各个节点:

我们的算法是:
1,根节点入队
2,出队一个节点,算一次遍历,直到队列为空
3,将刚出队的节点的子节点入队
4,转到2

队列的状况如下图:

// The Node

//////////////////////////////////////////////////////////////////////////

struct Node //树的每个节点

{

Node(char cChar, intiSubNodeNum=0);

~Node();

char m_cChar; //该节点编号例如(A.B.C……)

int m_iSubNodeNum; //该节点的子节点数目

Node**m_arrNodePointer; //指向子节点

};

Node::Node(char cChar, intiSubNodeNum)

{

m_cChar= cChar;

m_iSubNodeNum= iSubNodeNum;

if(iSubNodeNum!=0)

m_arrNodePointer= new Node*[iSubNodeNum];

else

m_arrNodePointer= NULL;

}

Node::~Node()

{

if(m_arrNodePointer!=NULL)

delete[] m_arrNodePointer;

}

// The Queue

//////////////////////////////////////////////////////////////////////////

class Queue

{

public:

Queue(int iAmount=10);

~Queue();

//return 0 means failed, return 1 means succeeded.

int Enqueue(Node* node);

int Dequeue(Node* & node);

private:

int m_iAmount;

int m_iCount;

Node**m_ppFixed; //The pointer array to implement thequeue.

int m_iHead;

int m_iTail;

};

Queue::Queue(int iAmount)

{

m_iCount= 0; //队列中已经使用的元素

m_iAmount= iAmount; //队列最大元素个数

m_ppFixed= new Node*[iAmount]; //初始化一个数组保存元素

m_iHead= 0; //头位置,即取元素位置,在数组开头

m_iTail= iAmount-1; //插入元素位置。在数组尾部

}

Queue::~Queue()

{

delete[] m_ppFixed;

}

int Queue::Enqueue(Node* node)

{

if(m_iCount<m_iAmount)

{

++m_iTail;

if(m_iTail > m_iAmount-1)

m_iTail= 0;

m_ppFixed[m_iTail]= node;

++m_iCount;

return 1;

}

else

return 0;

}

intQueue::Dequeue(Node* & node)

{

if(m_iCount>0)

{

node= m_ppFixed[m_iHead];

++m_iHead;

if(m_iHead > m_iAmount-1)

m_iHead= 0;

--m_iCount;

return 1;

}

else

return 0;

}

// Main

//////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[])

{

//Construct the tree.

NodenA('A', 3);

Node nB('B',2);

Node nC('C');

Node nD('D',3);

Node nE('E');

Node nF('F',2);

Node nG('G');

Node nH('H',1);

Node nI('I');

Node nJ('J');

Node nK('K');

Node nL('L');

nA.m_arrNodePointer[0] = &nB; //指向子节点

nA.m_arrNodePointer[1] = &nC;

nA.m_arrNodePointer[2] = &nD;

nB.m_arrNodePointer[0] = &nE;

nB.m_arrNodePointer[1] = &nF;

nD.m_arrNodePointer[0] = &nG;

nD.m_arrNodePointer[1] = &nH;

nD.m_arrNodePointer[2] = &nI;

nF.m_arrNodePointer[0] = &nJ;

nF.m_arrNodePointer[1]= &nK;

nH.m_arrNodePointer[0]= &nL;

Queueque;

que.Enqueue(&nA); //根节点A入队列

Node*pNode;

while (que.Dequeue(pNode)==1) //从队列中取出一个元素(第一次自然是A);第二次取出B

{

printf("%c ", pNode->m_cChar); //第一次输出A;第二次输出B

int i;

for(i=0; i<pNode->m_iSubNodeNum; i++) //第一次为A的三个子节点B,C,D入队列;第二次将B的子节点E,F加入队列。

{

que.Enqueue(pNode->m_arrNodePointer[i]);

}

}

return 0;

}

3 二分查找

//参数:有序数组、要查找的数字、最低位(一般为)、最高位(一般为数组最大下标)

static int

dichotomy_search(short a[], ems_int32 num, ems_int32 low, ems_int32high)

{

ems_int32mid;

if(low > high)

{

return FATAL_ERROR; //没有查到。。

}

mid= (low + high)/2; //中间

if(num == a[mid])

{

return mid; //找到

}

else

{

if(num < a[mid]) //在左边

{

return dichotomy_search(a, num, low, mid-1);

}

else //在右边

{

return dichotomy_search(a, num, mid+1, high);

}

}

}

4 散列

一般就是一个数组,通过散列算法定位到数组某个元素,当然这样可能会重复,你可以继续散列,或者做链表等等方法来处理。

1,除法散列法
最直观的一种,上图使用的就是这种散列法,公式:
index = value % 16
学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”。

2,平方散列法
求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
index = (value * value) >> 28
如果数值分配比较均匀的话这种方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。也许你还有个问题,value如果很大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。

可能有其他类型的输入,随机应变吧。

5 树

深度优先遍历又可分为:前序遍历(Preorder Traversal),后序遍历(Postorder Traversal)和中序遍历(Inorder Traversal),其中中序遍历只有对二叉树才有意义

//////////////////////////////////////////////////////////////////////////

struct TreeNode //树节点

{

char m_cVal; //节点值

TreeNode*m_pLeft; //左右子节点

TreeNode*m_pRight;

TreeNode(char cVal);

};

TreeNode::TreeNode(char cVal)

{

m_cVal= cVal;

m_pLeft= 0;

m_pRight= 0;

}

// A

// / \

// / \

// B C

// \ / \

// D E F

// \ \

// G H

// / \

// I J

// / \

// K L

// TreeNode

中序遍历:从A开始

TreeNode p = 根节点A;

while( p != NULL)

{

if(p有左节点)

{

P入栈 //1

p= p的左子节点//2

}

else if (p有右子节点)

{

输出P //1

p= p的右子节点//2

}

else //P无子节点了

{

输出P

如果栈里有值,弹出一个

如果栈里没有值了 break;

}

}

另外可以用递归实现,很简单。

如果需要查找和插入都很快,那么无疑应该选用二叉查找树。但是这种树的删除效率略低。如果用在只需要查找和插入的地方,比如构建属性表,空间信息数据则非常好。

但是,应该了解,二叉搜索树如果根节点的值选的不好或者插入的顺序不好,会使树非常之深,导致搜索插入效率急剧降低。那么就需要平衡二叉搜索树了。平衡二叉树的查找和删除和二叉搜索树一模一样。关键是构造的时候算法问题,主要分为4种情况,具体的算法可网上参阅。

6 二叉堆

一种特殊的队列,总是最小的元素先出。插入和取出都很快速,复杂度logn

基本概念:

1 二叉堆是一种特殊的完全二叉树,完全二叉树的最大特点在于不需要指针来表明左右节点。可以直接利用数组来保存完全二叉树,利用偏移来找到需要的元素。

2 其中最小的元素总在根节点。

3 入队原则:将新加入的元素放到树(数组)的最后面,然后依次和父节点比较,如果比父节点小,则交换位置。如此循环,知道无法交换为止。

4 出队原则:不好写。略

//交换个整形

//example:SWAP_TWO_INT(7,8)

// a = 0111^1000 =0000

// b = 1000^0000 =1000

// a = 0000^1000 =0111

#define SWAP_TWO_INT(a, b) \

a^=b;b^=a; a^=b;

class CBinaryHeap

{

public:

CBinaryHeap(int iSize = 100);

~CBinaryHeap();

int Enqueue(intiVal);

int Dequeue(int&iVal);

int GetMin(int&iVal);

#ifdef _DEBUG

void PrintQueue();

#endif

protected:

int *m_pData; //保存二叉堆的数组

int m_iSize; //二叉堆最大容量

int m_iAmount; //目前数目

};

CBinaryHeap::CBinaryHeap(int iSize)

{

m_pData= new int[iSize];

m_iSize= iSize;

m_iAmount= 0;

}

CBinaryHeap::~CBinaryHeap()

{

delete[] m_pData;

}

#ifdef _DEBUG

int CBinaryHeap::Enqueue(intiVal) //入队列

{

if(m_iAmount==m_iSize)

return 0;

//Put this value to the end of the array.

m_pData[m_iAmount]= iVal; //值放到数组最后,即二叉堆的最后

++m_iAmount;

int iIndex = m_iAmount - 1;

while(m_pData[iIndex] < m_pData[(iIndex-1)/2]) //循环和上一层比较,如果小于上一层则交换位置。

{

//Swap the two value

SWAP_TWO_INT(m_pData[iIndex],m_pData[(iIndex-1)/2])

iIndex= (iIndex-1)/2;//完全二叉树在数组中的位置固定

}

return 1;

}

#endif

int CBinaryHeap::Dequeue(int&iVal)//出队列

{

if(m_iAmount==0)

return 0;

iVal= m_pData[0]; //返回根节点

int iIndex = 0;

while (iIndex*2 < m_iAmount)

{

int iLeft = (iIndex*2+1 <m_iAmount)?(iIndex*2+1):0;

int iRight = (iIndex*2+2 <m_iAmount)?(iIndex*2+2):0;

if(iLeft && iRight) //Both left and right exists. 将根节点的子节点中较小的元素和根节点交换。

{

if(m_pData[iLeft]<m_pData[iRight])

{

SWAP_TWO_INT(m_pData[iIndex],m_pData[iLeft])

iIndex= iLeft;

}

else

{

SWAP_TWO_INT(m_pData[iIndex],m_pData[iRight])

iIndex= iRight;

}

}

else if(iLeft) //The iRight must be 0

{

SWAP_TWO_INT(m_pData[iIndex],m_pData[iLeft])

iIndex= iLeft;

break;

}

else

{

break;

}

}

//Move the last element to the blank position.

//Of course, if it is the blank one, forget it.

if(iIndex!=m_iAmount-1)

{

m_pData[iIndex]= m_pData[m_iAmount-1]; //将最后一个元素移到目前根节点所在的位置

//Try to move this element to the top as high as possible.

while(m_pData[iIndex] < m_pData[(iIndex-1)/2])

{

//Swap the two value

SWAP_TWO_INT(m_pData[iIndex],m_pData[(iIndex-1)/2])

iIndex= (iIndex-1)/2;

}

}

--m_iAmount;

return 1;

}

int CBinaryHeap::GetMin(int&iVal)

{

if(m_iAmount==0)

return 0;

iVal= m_pData[0];

return 1;

}

void CBinaryHeap::PrintQueue()

{

int i;

for(i=0; i<m_iAmount; i++)

{

printf("%d ", m_pData[i]);

}

printf("\n");

}

int main(int argc, char* argv[])

{

CBinaryHeapbh;

bh.Enqueue(4); //入队

bh.Enqueue(1);

bh.Enqueue(3);

bh.Enqueue(2);

bh.Enqueue(6);

bh.Enqueue(5);

#ifdef _DEBUG

bh.PrintQueue();

#endif

int iVal;

bh.Dequeue(iVal);//出队

bh.Dequeue(iVal);

#ifdef _DEBUG

bh.PrintQueue();

#endif

return 0;

}

7 排序算法

7.1 //冒泡排序

void BubblerSort(int *pArray,int iElementNum)

{

int i, j, x;

for(i=0; i<iElementNum-1; i++) //

{

for(j=0; j<iElementNum-1-i; j++)

{

if(pArray[j]>pArray[j+1])

{

x= pArray[j];

&nbs

p; pArray[j]= pArray[j+1];

pArray[j+1]= x;

}

}

}

}

//内部每一次循环将数组中最大的元素移到最后

//外部循环n-1次排序完毕,

//复杂度n*n/2 = n*n

7.2 //直接插入排序

//第一次取数组前个排序,第二次将第三个元素插入前面已经排好序的个数里面

//第三次将第个元素插入到前面个已排好序的元素里面

void StraightInsertionSort(int*pArray, int iElementNum)

{

int i, j, k;

for(i=0; i<iElementNum; i++)

{

int iHandling = pArray[i];

for(j=i; j>0; j--) //循环比较查找要插入的位置。。

{

if(iHandling>=pArray[j-1]) //找到要插入的位置

break;

}

for(k=i; k>j; k--) //将要插入的元素插入到指定位置,后面的元素依次顺移

pArray[k]= pArray[k-1];

pArray[j]= iHandling;

}

}

7.3 //二分插入排序

//和直接插入排序基本一样,只是在插入元素的时候利用了二分查找

void BinaryInsertionSort(int*pArray, int iElementNum)

{

int i, j, k;

for(i=0; i<iElementNum; i++)

{

int iHandling = pArray[i];

int iLeft = 0;

int iRight = i-1;

while(iLeft<=iRight) //二分查找要插入的位置

{

int iMiddle = (iLeft+iRight)/2;

if(iHandling < pArray[iMiddle])

{

iRight= iMiddle-1;

}

else if(iHandling> pArray[iMiddle])

{

iLeft= iMiddle+1;

}

else

{

j= iMiddle + 1;

break;

}

}

if(iLeft>iRight)

j= iLeft; //如果没有找到,即不需要移动位置了。

for(k=i; k>j; k--)

pArray[k]= pArray[k-1];

pArray[j]= iHandling;

}

}

//直接选择排序

//每循环一次把最大的元素取出来和最后一个元素交换

void StraightSelectionSort(int*pArray, int iElementNum)

{

int iEndIndex, i, iMaxIndex, x;

for(iEndIndex=iElementNum-1; iEndIndex>0;iEndIndex--)

{

for(i=0, iMaxIndex=0; i<iEndIndex; i++) //找出最大的元素

{

if(pArray[i]>=pArray[iMaxIndex])

iMaxIndex= i;

}

x= pArray[iMaxIndex]; //和最后一个元素交互

pArray[iMaxIndex]= pArray[iEndIndex];

pArray[iEndIndex]= x;

}

}

//快速排序,利用递归。

void QuickSort(int *pArray, int iElementNum)

{

int iTmp;

//Select the pivot make it to the right side.

int& iLeftIdx = pArray[0];

int& iRightIdx = pArray[iElementNum-1];

int& iMiddleIdx = pArray[(iElementNum-1)/2];

if(iLeftIdx>iMiddleIdx)

{

iTmp= iLeftIdx;

iLeftIdx= iMiddleIdx;

iMiddleIdx= iTmp;

}

if(iRightIdx>iMiddleIdx)

{

iTmp= iRightIdx;

iRightIdx= iMiddleIdx;

iMiddleIdx= iTmp;

}

if(iLeftIdx>iRightIdx)

{

iTmp= iLeftIdx;

iLeftIdx= iRightIdx;

iRightIdx= iTmp;

} //1:将左中右个元素的处于中间大小的元素放到数组的最后面,设为iPivot,为最开始的基础比较数据。

//2: 从数组第一个开始往后找到第一个大于iPivot的值,设为iLeft;从数组倒数第二个开始往前找到第一个小于iPivot的值iRight。然后交换iLeft和iRight。

//Make pivot's left element and right element.

int iLeft = 0;

int iRight = iElementNum-2;

int& iPivot = pArray[iElementNum-1];

while (1)

{

while (iLeft<iRight &&pArray[iLeft]<iPivot) ++iLeft;

while (iLeft<iRight &&pArray[iRight]>=iPivot) --iRight;

if(iLeft>=iRight)

break;

iTmp= pArray[iLeft];

pArray[iLeft]= pArray[iRight];

pArray[iRight]= iTmp; //交换iLeft和iRight

}

//Make the i

if(pArray[iLeft]>iPivot) //这次交换有2个目的 1 小的在前面 2使比较的元素更接近平均值

{

iTmp= pArray[iLeft];

pArray[iLeft]= iPivot;

iPivot= iTmp;

}

if(iLeft>1)

QuickSort(pArray,iLeft); //对前半部分排序

if(iElementNum-iLeft-1>=1)

QuickSort(&pArray[iLeft+1],iElementNum-iLeft-1); //对后半部分排序

}

//桶排序,例如buckets[100]=10 表示pArray中数值为100的元素有10个

void BucketSort(int *pArray, int iElementNum)
{
int buckets[RAND_MAX]; // RAND_MAX 这个值需要囊括所有的pArray中的元素,不好把握。
memset(buckets, 0, sizeof(buckets));
int i;
for(i=0; i<iElementNum; i++)
{
++buckets[pArray[i]-1];
}

int iAdded = 0;
for(i=0; i<RAND_MAX; i++)
{
while((buckets[i]--)>0)
{
pArray[iAdded++] = i;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: